mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
CmdPal: Did someone say grid pages? (#40832)
Closes #38269 Still working on this one, but essentially allows a list page to become a grid page by specifying a `GridProperties` property like so: ```C# public AllAppsPage() { PlaceholderText = Resources.search_installed_apps_placeholder; GridProperties = new MediumGridLayout(); } ``` > This is a very early version and very subject to change. Much to clean, but feedback & suggestions are welcome. ## Current preview ### SmallGridLayout <img width="998" height="607" alt="image" src="https://github.com/user-attachments/assets/ebdf11fd-6c86-4fc3-bf49-bcbb5d32caa4" /> ### MediumGridLayout <img width="976" height="586" alt="image" src="https://github.com/user-attachments/assets/82daa2e9-548e-4864-8885-1c486ca9f891" /> ### GalleryGridLayout <img width="988" height="600" alt="image" src="https://github.com/user-attachments/assets/23ca486a-35c7-467a-b200-4f6ee5f4a95c" /> --------- Co-authored-by: Mike Griese <migrie@microsoft.com>
This commit is contained in:
2
.github/actions/spell-check/allow/code.txt
vendored
2
.github/actions/spell-check/allow/code.txt
vendored
@@ -320,4 +320,4 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
INVOKEIDLIST
|
||||
|
||||
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -1451,7 +1451,6 @@ rstringalnum
|
||||
rstringalpha
|
||||
rstringdigit
|
||||
rtb
|
||||
RTB
|
||||
RTLREADING
|
||||
rtm
|
||||
runas
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IGalleryGridLayout> _model;
|
||||
|
||||
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
_model = new(galleryGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public bool ShowSubtitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
ShowSubtitle = model.ShowSubtitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IGridPropertiesViewModel
|
||||
{
|
||||
void InitializeProperties();
|
||||
}
|
||||
@@ -45,6 +45,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
(!_isFetching) &&
|
||||
IsLoading == false;
|
||||
|
||||
public bool IsGridView { get; private set; }
|
||||
|
||||
public IGridPropertiesViewModel? GridProperties { get; private set; }
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public bool ShowDetails { get; private set; }
|
||||
@@ -516,6 +520,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
_isDynamic = model is IDynamicListPage;
|
||||
|
||||
IsGridView = model.GridProperties is not null;
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
|
||||
ShowDetails = model.ShowDetails;
|
||||
UpdateProperty(nameof(ShowDetails));
|
||||
|
||||
@@ -537,9 +548,27 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
{
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return;
|
||||
@@ -583,7 +612,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
@@ -591,14 +620,20 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(GridProperties):
|
||||
IsGridView = model.GridProperties is not null;
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
break;
|
||||
case nameof(ShowDetails):
|
||||
this.ShowDetails = model.ShowDetails;
|
||||
ShowDetails = model.ShowDetails;
|
||||
break;
|
||||
case nameof(PlaceholderText):
|
||||
this._modelPlaceholderText = model.PlaceholderText;
|
||||
_modelPlaceholderText = model.PlaceholderText;
|
||||
break;
|
||||
case nameof(SearchText):
|
||||
this.SearchText = model.SearchText;
|
||||
SearchText = model.SearchText;
|
||||
break;
|
||||
case nameof(EmptyContent):
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMediumGridLayout> _model;
|
||||
|
||||
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
_model = new(mediumGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<ISmallGridLayout> _model;
|
||||
|
||||
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
_model = new(smallGridLayout);
|
||||
}
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public IGridPropertiesViewModel? GridProperties { get; set; }
|
||||
|
||||
public DataTemplate? Small { get; set; }
|
||||
|
||||
public DataTemplate? Medium { get; set; }
|
||||
|
||||
public DataTemplate? Gallery { get; set; }
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||
{
|
||||
DataTemplate? dataTemplate = Medium;
|
||||
|
||||
if (GridProperties is SmallGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Small;
|
||||
}
|
||||
else if (GridProperties is MediumGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Medium;
|
||||
}
|
||||
else if (GridProperties is GalleryGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Gallery;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
x:Class="Microsoft.CmdPal.UI.ListPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||
@@ -11,8 +12,11 @@
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
x:Name="PageRoot"
|
||||
Background="Transparent"
|
||||
DataContext="{x:Bind ViewModel, Mode=OneWay}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
@@ -23,6 +27,7 @@
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
@@ -39,6 +44,14 @@
|
||||
ToolTipService.ToolTip="{x:Bind ToolTip, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
|
||||
<cmdpalUI:GridItemTemplateSelector
|
||||
x:Key="GridItemTemplateSelector"
|
||||
x:DataType="coreViewModels:ListItemViewModel"
|
||||
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties}"
|
||||
Medium="{StaticResource MediumGridItemViewModelTemplate}"
|
||||
Small="{StaticResource SmallGridItemViewModelTemplate}" />
|
||||
|
||||
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
|
||||
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<Grid
|
||||
@@ -102,6 +115,145 @@
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Grid item templates for visual grid representation -->
|
||||
<DataTemplate x:Key="SmallGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="60"
|
||||
Height="60"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Width="28"
|
||||
Height="28"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="100"
|
||||
Height="100"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxHeight="40"
|
||||
Margin="0,8,0,4"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Title}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="160"
|
||||
Margin="4"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<Grid
|
||||
Width="160"
|
||||
Height="160"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Stretch="UniformToFill"
|
||||
StretchDirection="Both">
|
||||
<cpcontrols:IconBox
|
||||
CornerRadius="4"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Padding="4" Orientation="Vertical">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
Text="{x:Bind Title}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Name="SubTitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="11"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{x:Bind Subtitle}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
@@ -110,66 +262,50 @@
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.ShowEmptyContent, Mode=OneWay}">
|
||||
<controls:Case Value="False">
|
||||
<ListView
|
||||
x:Name="ItemsList"
|
||||
Padding="0,2,0,0"
|
||||
ContextCanceled="ItemsList_OnContextCanceled"
|
||||
ContextRequested="ItemsList_OnContextRequested"
|
||||
DoubleTapped="ItemsList_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ItemsList_ItemClick"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
SelectionChanged="ItemsList_SelectionChanged">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
<!--<ListView.GroupStyle>
|
||||
<GroupStyle HidesIfEmpty="True">
|
||||
<GroupStyle.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Margin="0,16,0,0"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Key, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</GroupStyle.HeaderTemplate>
|
||||
</GroupStyle>
|
||||
</ListView.GroupStyle>-->
|
||||
</ListView>
|
||||
</controls:Case>
|
||||
<controls:Case Value="True">
|
||||
<StackPanel
|
||||
Margin="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<cpcontrols:IconBox
|
||||
x:Name="IconBorder"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="8"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind ViewModel.EmptyContent.Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind ViewModel.EmptyContent.Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.EmptyContent.Subtitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<controls:SwitchPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.IsGridView, Mode=OneWay}">
|
||||
<controls:Case Value="False">
|
||||
<ListView
|
||||
x:Name="ItemsList"
|
||||
Padding="0,2,0,0"
|
||||
ContextCanceled="Items_OnContextCanceled"
|
||||
ContextRequested="Items_OnContextRequested"
|
||||
DoubleTapped="Items_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
SelectionChanged="Items_SelectionChanged">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
</controls:Case>
|
||||
<controls:Case Value="True">
|
||||
<GridView
|
||||
x:Name="ItemsGrid"
|
||||
Padding="8"
|
||||
ContextCanceled="Items_OnContextCanceled"
|
||||
ContextRequested="Items_OnContextRequested"
|
||||
DoubleTapped="Items_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
SelectionChanged="Items_SelectionChanged">
|
||||
<GridView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</GridView.ItemContainerTransitions>
|
||||
</GridView>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
@@ -38,13 +39,21 @@ public sealed partial class ListPage : Page,
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(ListViewModel), typeof(ListPage), new PropertyMetadata(null, OnViewModelChanged));
|
||||
|
||||
private ListViewBase ItemView
|
||||
{
|
||||
get
|
||||
{
|
||||
return ViewModel?.IsGridView == true ? ItemsGrid : ItemsList;
|
||||
}
|
||||
}
|
||||
|
||||
public ListPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
this.ItemsList.Loaded += ItemsList_Loaded;
|
||||
this.ItemsList.PreviewKeyDown += ItemsList_PreviewKeyDown;
|
||||
this.ItemsList.PointerPressed += ItemsList_PointerPressed;
|
||||
this.ItemView.Loaded += Items_Loaded;
|
||||
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
|
||||
this.ItemView.PointerPressed += Items_PointerPressed;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
@@ -55,11 +64,11 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
if (e.NavigationMode == NavigationMode.Back
|
||||
|| (e.NavigationMode == NavigationMode.New && ItemsList.Items.Count > 0))
|
||||
|| (e.NavigationMode == NavigationMode.New && ItemView.Items.Count > 0))
|
||||
{
|
||||
// Upon navigating _back_ to this page, immediately select the
|
||||
// first item in the list
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
// RegisterAll isn't AOT compatible
|
||||
@@ -90,7 +99,6 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.SafeCleanup();
|
||||
CleanupHelper.Cleanup(this);
|
||||
Bindings.StopTracking();
|
||||
}
|
||||
|
||||
// Clean-up event listeners
|
||||
@@ -100,7 +108,7 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
private void ItemsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
private void Items_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is ListItemViewModel item)
|
||||
{
|
||||
@@ -123,9 +131,9 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemsList_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
private void Items_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
if (ItemsList.SelectedItem is ListItemViewModel vm)
|
||||
if (ItemView.SelectedItem is ListItemViewModel vm)
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (!settings.SingleClickActivates)
|
||||
@@ -136,10 +144,10 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
var li = ItemsList.SelectedItem as ListItemViewModel;
|
||||
var li = ItemView.SelectedItem as ListItemViewModel;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
vm?.UpdateSelectedItemCommand.Execute(li);
|
||||
@@ -154,12 +162,12 @@ public sealed partial class ListPage : Page,
|
||||
// here, then in Page_ItemsUpdated trying to select that cached item if
|
||||
// it's in the list (otherwise, clear the cache), but that seems
|
||||
// aggressively BODGY for something that mostly just works today.
|
||||
if (ItemsList.SelectedItem is not null)
|
||||
if (ItemView.SelectedItem is not null)
|
||||
{
|
||||
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
|
||||
ItemView.ScrollIntoView(ItemView.SelectedItem);
|
||||
|
||||
// Automation notification for screen readers
|
||||
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemsList);
|
||||
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemView);
|
||||
if (listViewPeer is not null && li is not null)
|
||||
{
|
||||
var notificationText = li.Title;
|
||||
@@ -172,10 +180,37 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemsList_Loaded(object sender, RoutedEventArgs e)
|
||||
private void Items_RightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
// Find the ScrollViewer in the ListView
|
||||
var listViewScrollViewer = FindScrollViewer(this.ItemsList);
|
||||
if (e.OriginalSource is FrameworkElement element &&
|
||||
element.DataContext is ListItemViewModel item)
|
||||
{
|
||||
if (ItemView.SelectedItem != item)
|
||||
{
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
|
||||
var pos = e.GetPosition(element);
|
||||
|
||||
_ = DispatcherQueue.TryEnqueue(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
|
||||
new OpenContextMenuMessage(
|
||||
element,
|
||||
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
pos,
|
||||
ContextMenuFilterLocation.Top));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void Items_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Find the ScrollViewer in the ItemView (ItemsList or ItemsGrid)
|
||||
var listViewScrollViewer = FindScrollViewer(this.ItemView);
|
||||
|
||||
if (listViewScrollViewer is not null)
|
||||
{
|
||||
@@ -207,25 +242,25 @@ public sealed partial class ListPage : Page,
|
||||
// And then have these commands manipulate that state being bound to the UI instead
|
||||
// We may want to see how other non-list UIs need to behave to make this decision
|
||||
// At least it's decoupled from the SearchBox now :)
|
||||
if (ItemsList.SelectedIndex < ItemsList.Items.Count - 1)
|
||||
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
|
||||
{
|
||||
ItemsList.SelectedIndex++;
|
||||
ItemView.SelectedIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(NavigatePreviousCommand message)
|
||||
{
|
||||
if (ItemsList.SelectedIndex > 0)
|
||||
if (ItemView.SelectedIndex > 0)
|
||||
{
|
||||
ItemsList.SelectedIndex--;
|
||||
ItemView.SelectedIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsList.SelectedIndex = ItemsList.Items.Count - 1;
|
||||
ItemView.SelectedIndex = ItemView.Items.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +270,7 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(null);
|
||||
}
|
||||
else if (ItemsList.SelectedItem is ListItemViewModel item)
|
||||
else if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
@@ -247,7 +282,7 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.InvokeSecondaryCommandCommand.Execute(null);
|
||||
}
|
||||
else if (ItemsList.SelectedItem is ListItemViewModel item)
|
||||
else if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeSecondaryCommandCommand.Execute(item);
|
||||
}
|
||||
@@ -283,19 +318,19 @@ public sealed partial class ListPage : Page,
|
||||
//
|
||||
// It's important to do this here, because once there's no selection
|
||||
// (which can happen as the list updates) we won't get an
|
||||
// ItemsList_SelectionChanged again to give us another chance to change
|
||||
// ItemView_SelectionChanged again to give us another chance to change
|
||||
// the selection from null -> something. Better to just update the
|
||||
// selection once, at the end of all the updating.
|
||||
if (ItemsList.SelectedItem is null)
|
||||
if (ItemView.SelectedItem is null)
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
// Always reset the selected item when the top-level list page changes
|
||||
// its items
|
||||
if (!sender.IsNested)
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +339,7 @@ public sealed partial class ListPage : Page,
|
||||
var prop = e.PropertyName;
|
||||
if (prop == nameof(ViewModel.FilteredItems))
|
||||
{
|
||||
Debug.WriteLine($"ViewModel.FilteredItems {ItemsList.SelectedItem}");
|
||||
Debug.WriteLine($"ViewModel.FilteredItems {ItemView.SelectedItem}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,12 +363,12 @@ public sealed partial class ListPage : Page,
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ItemsList_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
|
||||
private void Items_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
var (item, element) = e.OriginalSource switch
|
||||
{
|
||||
// caused by keyboard shortcut (e.g. Context menu key or Shift+F10)
|
||||
ListViewItem listViewItem => (ItemsList.ItemFromContainer(listViewItem) as ListItemViewModel, listViewItem),
|
||||
SelectorItem selectorItem => (ItemView.ItemFromContainer(selectorItem) as ListItemViewModel, selectorItem),
|
||||
|
||||
// caused by right-click on the ListViewItem
|
||||
FrameworkElement { DataContext: ListItemViewModel itemViewModel } frameworkElement => (itemViewModel, frameworkElement),
|
||||
@@ -346,9 +381,9 @@ public sealed partial class ListPage : Page,
|
||||
return;
|
||||
}
|
||||
|
||||
if (ItemsList.SelectedItem != item)
|
||||
if (ItemView.SelectedItem != item)
|
||||
{
|
||||
ItemsList.SelectedItem = item;
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
@@ -371,14 +406,14 @@ public sealed partial class ListPage : Page,
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ItemsList_OnContextCanceled(UIElement sender, RoutedEventArgs e)
|
||||
private void Items_OnContextCanceled(UIElement sender, RoutedEventArgs e)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
|
||||
}
|
||||
|
||||
private void ItemsList_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
|
||||
private void Items_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
|
||||
|
||||
private void ItemsList_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
private void Items_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key is VirtualKey.Enter or VirtualKey.Space)
|
||||
{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 584 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 613 KiB |
@@ -0,0 +1,66 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleGalleryListPage : ListPage
|
||||
{
|
||||
public SampleGalleryListPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE7C5");
|
||||
Name = "Sample Gallery List Page";
|
||||
GridProperties = new GalleryGridLayout();
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Sample Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Another Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "More Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Stop With The Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Another Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "More Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Stop With The Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,11 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Dynamic List Page Command",
|
||||
Subtitle = "Changes the list of items in response to the typed query",
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage())
|
||||
{
|
||||
Title = "Gallery List Page Command",
|
||||
Subtitle = "Displays items as a gallery",
|
||||
},
|
||||
new ListItem(new OnLoadPage())
|
||||
{
|
||||
Title = "Demo of OnLoad/OnUnload",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class GalleryGridLayout : BaseObservable, IGalleryGridLayout
|
||||
{
|
||||
public virtual bool ShowTitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
|
||||
public virtual bool ShowSubtitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowSubtitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class MediumGridLayout : BaseObservable, IMediumGridLayout
|
||||
{
|
||||
public virtual bool ShowTitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class SmallGridLayout : BaseObservable, ISmallGridLayout
|
||||
{
|
||||
}
|
||||
@@ -273,10 +273,26 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
String Section { get; };
|
||||
String TextToSuggest { get; };
|
||||
}
|
||||
|
||||
[uuid("50C6F080-1CBE-4CE4-B92F-DA2F116ED524")]
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGridProperties requires INotifyPropChanged { }
|
||||
|
||||
[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface ISmallGridLayout requires IGridProperties { }
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGridProperties {
|
||||
Windows.Foundation.Size TileSize { get; };
|
||||
interface IMediumGridLayout requires IGridProperties
|
||||
{
|
||||
Boolean ShowTitle { get; };
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGalleryGridLayout requires IGridProperties
|
||||
{
|
||||
Boolean ShowTitle { get; };
|
||||
Boolean ShowSubtitle { get; };
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
|
||||
Reference in New Issue
Block a user