Merge remote-tracking branch 'origin/main' into dev/migrie/f/TRA-forms-pr

This commit is contained in:
Mike Griese
2024-12-13 08:34:20 -06:00
8 changed files with 265 additions and 182 deletions

View File

@@ -3,14 +3,14 @@
x:Class="Microsoft.CmdPal.UI.Controls.ActionBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
xmlns:local="using:Microsoft.CmdPal.UI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
Background="Transparent"
mc:Ignorable="d">
@@ -30,16 +30,13 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border x:Name="IconBorder"
Grid.Column="0"
Width="16"
Height="16"
Margin="0,0,0,0">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<cmdpalUI:LoadIconBehavior Source="{x:Bind Icon, Mode=OneWay}"/>
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
Grid.Column="0"
Width="16"
Height="16"
Margin="0,0,0,0"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock Grid.Column="1" Text="{x:Bind Title}" />
</Grid>
@@ -55,18 +52,15 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="IconBorder"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Width="20"
Height="20"
Margin="12,0,0,0"
CornerRadius="{StaticResource ControlCornerRadius}">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<cmdpalUI:LoadIconBehavior Source="{x:Bind CurrentPageViewModel.Icon, Mode=OneWay}"/>
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
x:Name="IconBorder"
Width="20"
Height="20"
Margin="12,0,0,0"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
CornerRadius="{StaticResource ControlCornerRadius}"
SourceKey="{x:Bind CurrentPageViewModel.Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
Grid.Column="1"
@@ -81,21 +75,19 @@
Spacing="6">
<Button
x:Name="PrimaryButton"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Height="40"
Padding="8,4,8,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Visibility="{x:Bind ViewModel.PrimaryAction.Name, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8">
<!-- <FontIcon Glyph="&#xEA3A;" /> -->
<Border Width="16"
Height="16"
Margin="4,4,4,4">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<cmdpalUI:LoadIconBehavior Source="{x:Bind ViewModel.PrimaryAction.Icon, Mode=OneWay}"/>
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
Width="16"
Height="16"
Margin="4,4,4,4"
SourceKey="{x:Bind ViewModel.PrimaryAction.Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<StackPanel Orientation="Vertical" Spacing="2">
<TextBlock
@@ -113,21 +105,19 @@
</Button>
<Button
x:Name="SecondaryButton"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Height="40"
Padding="8,4,8,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Visibility="{x:Bind ViewModel.HasSecondaryCommand, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="8">
<!-- <FontIcon Glyph="&#xEA3A;" /> -->
<Border Width="16"
Height="16"
Margin="4,4,4,4">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<cmdpalUI:LoadIconBehavior Source="{x:Bind ViewModel.SecondaryAction.Icon, Mode=OneWay}" />
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
Width="16"
Height="16"
Margin="4,4,4,4"
SourceKey="{x:Bind ViewModel.SecondaryAction.Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<StackPanel Orientation="Vertical" Spacing="1">
<TextBlock

View File

@@ -0,0 +1,110 @@
// 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 CommunityToolkit.Common.Deferred;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Deferred;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// A helper control which takes an <see cref="IconSource"/> and creates the corresponding <see cref="IconElement"/>.
/// </summary>
public partial class IconBox : ContentControl
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
/// <summary>
/// Gets or sets the <see cref="IconSource"/> to display within the <see cref="IconBox"/>. Overwritten, if <see cref="SourceKey"/> is used instead.
/// </summary>
public IconSource? Source
{
get => (IconSource?)GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(IconSource), typeof(IconBox), new PropertyMetadata(null, OnSourcePropertyChanged));
/// <summary>
/// Gets or sets a value to use as the <see cref="SourceKey"/> to retrieve an <see cref="IconSource"/> to set as the <see cref="Source"/>.
/// </summary>
public object? SourceKey
{
get => (object?)GetValue(SourceKeyProperty);
set => SetValue(SourceKeyProperty, value);
}
// Using a DependencyProperty as the backing store for SourceKey. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceKeyProperty =
DependencyProperty.Register(nameof(SourceKey), typeof(object), typeof(IconBox), new PropertyMetadata(null, OnSourceKeyPropertyChanged));
/// <summary>
/// Gets or sets the <see cref="SourceRequested"/> event handler to provide the value of the <see cref="IconSource"/> for the <see cref="Source"/> property from the provided <see cref="SourceKey"/>.
/// </summary>
public event TypedEventHandler<IconBox, SourceRequestedEventArgs>? SourceRequested;
private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is IconBox @this)
{
switch (e.NewValue)
{
case null:
@this.Content = null;
break;
case FontIconSource fontIco:
fontIco.FontSize = @this.Width;
// For inexplicable reasons, FontIconSource.CreateIconElement
// doesn't work, so do it ourselves
// TODO: File platform bug?
IconSourceElement elem = new()
{
IconSource = fontIco,
};
@this.Content = elem;
break;
case IconSource source:
@this.Content = source.CreateIconElement();
break;
default:
throw new InvalidOperationException($"New value of {e.NewValue} is not of type IconSource.");
}
}
}
private static void OnSourceKeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is IconBox @this)
{
if (e.NewValue == null)
{
@this.Source = null;
}
else
{
_ = @this._queue.EnqueueAsync(async () =>
{
var eventArgs = new SourceRequestedEventArgs(e.NewValue);
if (@this.SourceRequested != null)
{
await @this.SourceRequested.InvokeAsync(@this, eventArgs);
if (eventArgs.Value != null)
{
@this.Source = eventArgs.Value;
}
}
});
}
}
}
}

View File

@@ -0,0 +1,18 @@
// 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 CommunityToolkit.Common.Deferred;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// See <see cref="IconBox.SourceRequested"/> event.
/// </summary>
public class SourceRequestedEventArgs(object? key) : DeferredEventArgs
{
public object? Key { get; private set; } = key;
public IconSource? Value { get; set; }
}

View File

@@ -3,11 +3,11 @@
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:Interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
@@ -33,25 +33,23 @@
Spacing="8" />
<DataTemplate x:Key="TagTemplate" x:DataType="viewmodels:TagViewModel">
<!-- TODO: Actually colorize the tags again -->
<!-- TODO: Actually colorize the tags again -->
<StackPanel
Padding="4,2,4,2"
VerticalAlignment="Center"
BorderBrush="{ThemeResource TextBoxBorderThemeBrush}"
BorderThickness="1"
Orientation="Horizontal"
CornerRadius="4">
<Border x:Name="IconBorder"
Width="12"
Height="12"
Margin="0,0,4,0"
Visibility="{x:Bind HasIcon, Mode=OneWay}">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<local:LoadIconBehavior Source="{x:Bind Icon, Mode=OneWay}"/>
</Interactivity:Interaction.Behaviors>
</Border>
CornerRadius="4"
Orientation="Horizontal">
<cpcontrols:IconBox
x:Name="IconBorder"
Width="12"
Height="12"
Margin="0,0,4,0"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
Visibility="{x:Bind HasIcon, Mode=OneWay}" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
@@ -72,16 +70,14 @@
</Grid.ColumnDefinitions>
<Border x:Name="IconBorder"
Grid.Column="0"
Width="20"
Height="20"
Margin="4,0,4,0">
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<local:LoadIconBehavior Source="{x:Bind Icon, Mode=OneWay}"/>
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
x:Name="IconBorder"
Grid.Column="0"
Width="20"
Height="20"
Margin="4,0,4,0"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<StackPanel
Grid.Column="1"
@@ -105,12 +101,13 @@
TextWrapping="NoWrap"
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" />
</StackPanel>
<ItemsRepeater ItemTemplate="{StaticResource TagTemplate}"
ItemsSource="{x:Bind Tags}"
Grid.Column="2"
Visibility="{x:Bind HasTags}"
Layout="{StaticResource HorizontalStackLayout}" />
<ItemsRepeater
Grid.Column="2"
ItemTemplate="{StaticResource TagTemplate}"
ItemsSource="{x:Bind Tags}"
Layout="{StaticResource HorizontalStackLayout}"
Visibility="{x:Bind HasTags}" />
</Grid>
</ListViewItem>
</DataTemplate>

View File

@@ -0,0 +1,36 @@
// 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.Extensions;
using Microsoft.CmdPal.UI.Controls;
using Microsoft.CmdPal.UI.ExtViews;
namespace Microsoft.CmdPal.UI.Helpers;
/// <summary>
/// Common async event handler provides the cache lookup function for the <see cref="IconBox.SourceRequested"/> deferred event.
/// </summary>
public static partial class IconCacheProvider
{
private static readonly IconCacheService IconService = new(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
#pragma warning disable IDE0060 // Remove unused parameter
public static async void SourceRequested(IconBox sender, SourceRequestedEventArgs args)
#pragma warning restore IDE0060 // Remove unused parameter
{
if (args.Key == null)
{
return;
}
if (args.Key is IconDataType iconData)
{
var deferral = args.GetDeferral();
args.Value = await IconService.GetIconSource(iconData);
deferral.Complete();
}
}
}

View File

@@ -1,66 +0,0 @@
// 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.Extensions;
using Microsoft.CmdPal.UI.ExtViews;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Xaml.Interactivity;
namespace Microsoft.CmdPal.UI;
public partial class LoadIconBehavior : DependencyObject, IBehavior
{
private static readonly IconCacheService IconService = new(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
public IconDataType? Source
{
get => (IconDataType?)GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(IconDataType), typeof(LoadIconBehavior), new PropertyMetadata(null, OnSourcePropertyChanged));
public DependencyObject? AssociatedObject { get; private set; }
public void Attach(DependencyObject associatedObject) => AssociatedObject = associatedObject;
public void Detach() => AssociatedObject = null;
private static async void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LoadIconBehavior @this
&& @this.AssociatedObject is Border border)
{
var icoSource = await IconService.GetIconSource(@this.Source ?? new(string.Empty));
/* This causes a catastrophic failure...
if (border.Child != null)
{
VisualTreeHelper.DisconnectChildrenRecursive(border.Child);
border.Child = null;
}*/
if (icoSource is FontIconSource fontIco)
{
fontIco.FontSize = border.Width;
// For inexplicable reasons, FontIconSource.CreateIconElement
// doesn't work, so do it ourselves
IconSourceElement elem = new()
{
IconSource = fontIco,
};
border.Child = elem;
}
else
{
var icoElement = icoSource?.CreateIconElement();
border.Child = icoElement;
}
}
}
}

View File

@@ -3,13 +3,12 @@
x:Class="Microsoft.CmdPal.UI.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:labs="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:labs="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="Transparent"
mc:Ignorable="d">
@@ -32,7 +31,7 @@
<VisualState x:Name="DetailsVisible">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsDetailsVisible, Mode=OneWay}"/>
<StateTrigger IsActive="{x:Bind ViewModel.IsDetailsVisible, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="DetailsContent.Visibility" Value="Visible" />
@@ -49,13 +48,14 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:SearchBar
<cpcontrols:SearchBar
x:Name="SearchBox"
Grid.Row="0"
VerticalAlignment="Top"
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
<Grid x:Name="ContentGrid"
<Grid
x:Name="ContentGrid"
Grid.Row="1"
Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
@@ -66,18 +66,19 @@
<ColumnDefinition x:Name="DetailsColumn" Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Name="RootFrame"
Grid.Column="0"
IsNavigationStackEnabled="True" />
<Frame
Name="RootFrame"
Grid.Column="0"
IsNavigationStackEnabled="True" />
<Grid
<Grid
x:Name="DetailsContent"
Grid.Column="1"
Visibility="Collapsed"
Grid.Column="1"
Padding="8"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1,0,0,0">
BorderThickness="1,0,0,0"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -85,40 +86,37 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="HeroImageBorder"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
Width="64"
Height="64"
Visibility="{x:Bind ViewModel.Details.HasHeroImage, Mode=OneWay}" >
<!-- LoadIconBehavior will magically fill this border up with an icon -->
<Interactivity:Interaction.Behaviors>
<local:LoadIconBehavior Source="{x:Bind ViewModel.Details.HeroImage, Mode=OneWay}" />
</Interactivity:Interaction.Behaviors>
</Border>
<cpcontrols:IconBox
x:Name="HeroImageBorder"
Width="64"
Height="64"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
SourceKey="{x:Bind ViewModel.Details.HeroImage, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
Visibility="{x:Bind ViewModel.Details.HasHeroImage, Mode=OneWay}" />
<TextBlock
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
FontSize="20"
TextWrapping="WrapWholeWords"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.Details.Title, Mode=OneWay}"
Visibility="{x:Bind ViewModel.Details.Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}"/>
Text="{x:Bind ViewModel.Details.Title, Mode=OneWay}"
TextWrapping="WrapWholeWords"
Visibility="{x:Bind ViewModel.Details.Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
<ScrollViewer Grid.Row="2" HorizontalAlignment="Stretch">
<labs:MarkdownTextBlock
<labs:MarkdownTextBlock
x:Name="DetailsMarkdown"
Text="{x:Bind ViewModel.Details.Body, Mode=OneWay}"
Background="Transparent" >
</labs:MarkdownTextBlock>
Background="Transparent"
Text="{x:Bind ViewModel.Details.Body, Mode=OneWay}" />
</ScrollViewer>
</Grid> <!--/DetailsContent-->
</Grid>
<!-- /DetailsContent -->
</Grid>
<controls:ActionBar
<cpcontrols:ActionBar
Grid.Row="2"
VerticalAlignment="Top"
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />