Merge branch 'dev' into explorerMerge

This commit is contained in:
Kevin Zhang 2022-11-11 11:14:42 -06:00 committed by GitHub
commit 36a35a1c01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 982 additions and 410 deletions

View file

@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using NLog;

View file

@ -0,0 +1,65 @@
using System;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public abstract class ShortcutBaseModel
{
public string Key { get; set; }
[JsonIgnore]
public Func<string> Expand { get; set; } = () => { return ""; };
public override bool Equals(object obj)
{
return obj is ShortcutBaseModel other &&
Key == other.Key;
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
}
public class CustomShortcutModel : ShortcutBaseModel
{
public string Value { get; set; }
[JsonConstructorAttribute]
public CustomShortcutModel(string key, string value)
{
Key = key;
Value = value;
Expand = () => { return Value; };
}
public void Deconstruct(out string key, out string value)
{
key = Key;
value = Value;
}
public static implicit operator (string Key, string Value)(CustomShortcutModel shortcut)
{
return (shortcut.Key, shortcut.Value);
}
public static implicit operator CustomShortcutModel((string Key, string Value) shortcut)
{
return new CustomShortcutModel(shortcut.Key, shortcut.Value);
}
}
public class BuiltinShortcutModel : ShortcutBaseModel
{
public string Description { get; set; }
public BuiltinShortcutModel(string key, string description, Func<string> expand)
{
Key = key;
Description = description;
Expand = expand ?? (() => { return ""; });
}
}
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Text.Json.Serialization;
using System.Windows;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher;
@ -129,8 +130,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
PrivateArg = "-private",
EnablePrivate = false,
Editable = false
}
,
},
new()
{
Name = "MS Edge",
@ -186,6 +186,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
public ObservableCollection<CustomShortcutModel> CustomShortcuts { get; set; } = new ObservableCollection<CustomShortcutModel>();
[JsonIgnore]
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new ObservableCollection<BuiltinShortcutModel>() {
new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText)
};
public bool DontPromptUpdateMsg { get; set; }
public bool EnableUpdateLog { get; set; }

View file

@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows.Data;
using Flow.Launcher.Core.Resource;
namespace Flow.Launcher.Converters
{
public class TranlationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value.ToString();
if (String.IsNullOrEmpty(key))
return key;
return InternationalizationManager.Instance.GetTranslation(key);
}
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
}
}

View file

@ -0,0 +1,160 @@
<Window
x:Class="Flow.Launcher.CustomShortcutSetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
Title="{DynamicResource customeQueryShortcutTitle}"
Width="530"
Background="{DynamicResource PopuBGColor}"
Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterScreen"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="Close" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="Close" Executed="cmdEsc_OnPress" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="4"
Click="BtnCancel_OnClick"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
</Grid>
</StackPanel>
<StackPanel Margin="26,0,26,0">
<StackPanel Grid.Row="0" Margin="0,0,0,12">
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
FontFamily="Segoe UI"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource customQueryShortcut}"
TextAlignment="Left" />
</StackPanel>
<StackPanel>
<TextBlock
FontSize="14"
Text="{DynamicResource customeQueryShortcutTips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<Grid Width="470">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource customShortcut}" />
<TextBox
Grid.Row="0"
Grid.Column="1"
Margin="10"
Text="{Binding Key}"
/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource customShortcutExpansion}" />
<DockPanel
Grid.Row="1"
Grid.Column="1"
LastChildFill="True">
<Button
x:Name="btnTestShortcut"
Margin="0,0,10,0"
Padding="10,5,10,5"
Click="BtnTestShortcut_OnClick"
Content="{DynamicResource preview}"
DockPanel.Dock="Right" />
<TextBox
x:Name="tbExpand"
Margin="10"
HorizontalAlignment="Stretch"
Text="{Binding Value}"
VerticalAlignment="Center" />
</DockPanel>
</Grid>
</StackPanel>
</StackPanel>
</StackPanel>
<Border
Grid.Row="1"
Margin="0,14,0,0"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0,1,0,0">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
MinWidth="140"
Margin="10,0,5,0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnAdd"
MinWidth="140"
Margin="5,0,10,0"
Click="BtnAdd_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
</Button>
</StackPanel>
</Border>
</Grid>
</Window>

View file

@ -0,0 +1,73 @@
using Flow.Launcher.Core.Resource;
using Flow.Launcher.ViewModel;
using System;
using System.Windows;
using System.Windows.Input;
namespace Flow.Launcher
{
public partial class CustomShortcutSetting : Window
{
private SettingWindowViewModel viewModel;
public string Key { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty;
private string originalKey { get; init; } = null;
private string originalValue { get; init; } = null;
private bool update { get; init; } = false;
public CustomShortcutSetting(SettingWindowViewModel vm)
{
viewModel = vm;
InitializeComponent();
}
public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm)
{
viewModel = vm;
Key = key;
Value = value;
originalKey = key;
originalValue = value;
update = true;
InitializeComponent();
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
{
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
return;
}
// Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update)
&& viewModel.ShortcutExists(Key))
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
return;
}
DialogResult = !update || originalKey != Key || originalValue != Value;
Close();
}
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e)
{
App.API.ChangeQuery(tbExpand.Text);
Application.Current.MainWindow.Show();
Application.Current.MainWindow.Opacity = 1;
Application.Current.MainWindow.Focus();
}
}
}

View file

@ -98,6 +98,7 @@
</PackageReference>
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="SharpVectors" Version="1.7.6" />
<PackageReference Include="VirtualizingWrapPanel" Version="1.5.7" />
</ItemGroup>
<ItemGroup>
@ -120,7 +121,7 @@
<!-- Work around https://github.com/dotnet/wpf/issues/6792 -->
<ItemGroup>
<FilteredAnalyzer Include="@(Analyzer->Distinct())" />
<FilteredAnalyzer Include="@(Analyzer-&gt;Distinct())" />
<Analyzer Remove="@(Analyzer)" />
<Analyzer Include="@(FilteredAnalyzer)" />
</ItemGroup>

View file

@ -144,12 +144,18 @@
<system:String x:Key="showOpenResultHotkey">Show Hotkey</system:String>
<system:String x:Key="showOpenResultHotkeyToolTip">Show result selection hotkey with results.</system:String>
<system:String x:Key="customQueryHotkey">Custom Query Hotkey</system:String>
<system:String x:Key="customQueryShortcut">Custom Query Shortcut</system:String>
<system:String x:Key="customQuery">Query</system:String>
<system:String x:Key="customShortcut">Shortcut</system:String>
<system:String x:Key="customShortcutExpansion">Expanded</system:String>
<system:String x:Key="builtinShortcutDescription">Description</system:String>
<system:String x:Key="delete">Delete</system:String>
<system:String x:Key="edit">Edit</system:String>
<system:String x:Key="add">Add</system:String>
<system:String x:Key="pleaseSelectAnItem">Please select an item</system:String>
<system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String>
<system:String x:Key="deleteCustomShortcutWarning">Are you sure you want to delete shortcut: {0} with expansion {1}?</system:String>
<system:String x:Key="shortcut_clipboard_description">Get text from clipboard.</system:String>
<system:String x:Key="queryWindowShadowEffect">Query window shadow effect</system:String>
<system:String x:Key="shadowEffectCPUUsage">Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited.</system:String>
<system:String x:Key="windowWidthSize">Window Width Size</system:String>
@ -241,6 +247,12 @@
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
<system:String x:Key="update">Update</system:String>
<!-- Custom Query Shortcut Dialog -->
<system:String x:Key="customeQueryShortcutTitle">Custom Query Shortcut</system:String>
<system:String x:Key="customeQueryShortcutTips">Enter a shortcut that automatically expands to the specified query.</system:String>
<system:String x:Key="duplicateShortcut">Shortcut already exists, please enter a new Shortcut or edit the existing one.</system:String>
<system:String x:Key="emptyShortcut">Shortcut and/or its expansion is empty.</system:String>
<!-- Hotkey Control -->
<system:String x:Key="hotkeyUnavailable">Hotkey Unavailable</system:String>

View file

@ -69,6 +69,8 @@
<SolidColorBrush x:Key="CustomContextDisabled" Color="#868686" />
<SolidColorBrush x:Key="ContextSeparator" Color="#3c3c3c" />
<SolidColorBrush x:Key="HoverStoreGrid2">#272727</SolidColorBrush>
<Color x:Key="Color01">#202020</Color>
<Color x:Key="Color02">#2b2b2b</Color>
<Color x:Key="Color03">#1d1d1d</Color>

View file

@ -62,6 +62,8 @@
<SolidColorBrush x:Key="CustomContextDisabled" Color="#868686" />
<SolidColorBrush x:Key="ContextSeparator" Color="#dadada" />
<SolidColorBrush x:Key="HoverStoreGrid2">#f6f6f6</SolidColorBrush>
<Color x:Key="Color01">#f3f3f3</Color>
<Color x:Key="Color02">#ffffff</Color>
<Color x:Key="Color03">#e5e5e5</Color>

View file

@ -11,6 +11,7 @@
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
Title="{DynamicResource flowlauncher_settings}"
Width="{Binding SettingWindowWidth, Mode=TwoWay}"
Height="{Binding SettingWindowHeight, Mode=TwoWay}"
@ -41,12 +42,31 @@
<converters:BorderClipConverter x:Key="BorderClipConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:TextConverter x:Key="TextConverter" />
<converters:TranlationConverter x:Key="TranlationConverter" />
<CollectionViewSource x:Key="SortedFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="PluginStoreCollectionView"
Source="{Binding ExternalPlugins}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category"></PropertyGroupDescription>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="StoreItemFocusVisualStyleKey">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle
Margin="0"
Stroke="Black"
StrokeThickness="2" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingGrid" TargetType="ItemsControl">
<Setter Property="Focusable" Value="False" />
<Setter Property="Margin" Value="0" />
@ -129,6 +149,7 @@
<Setter Property="Margin" Value="0,0,-18,0" />
</Style>
<Style x:Key="logo" TargetType="{x:Type TabItem}">
<!--#region Logo Style-->
<Setter Property="Margin" Value="0" />
@ -242,11 +263,7 @@
</Style>
<!-- Plugin Store Item when Selected layout for nothing -->
<Style x:Key="StoreItem" TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True" />
</Style.Triggers>
</Style>
<Style x:Key="StoreItem" TargetType="{x:Type ToggleButton}" />
<Style x:Key="PluginList" TargetType="ListBoxItem">
<Setter Property="Background" Value="{DynamicResource Color00B}" />
@ -319,11 +336,12 @@
</Style>
<!--#region PluginStore Style-->
<Style x:Key="StoreList" TargetType="ListBoxItem">
<Setter Property="Background" Value="{DynamicResource Color02B}" />
<Style x:Key="StoreList" TargetType="ListViewItem">
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="Margin" Value="0,0,8,5" />
<Setter Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,8,8" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<!--#region Template for blue highlight win10-->
<Setter Property="Template">
<Setter.Value>
@ -335,7 +353,8 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"
SnapsToDevicePixels="True">
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
@ -344,43 +363,6 @@
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource Color07B}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter TargetName="Bd" Property="CornerRadius" Value="5" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource Color02B}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter TargetName="Bd" Property="Margin" Value="0,0,0,0" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource Color02B}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter TargetName="Bd" Property="CornerRadius" Value="5" />
<Setter TargetName="Bd" Property="Margin" Value="0,0,0,0" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
@ -484,7 +466,6 @@
Width="16"
Height="16"
Margin="10,4,4,4"
RenderOptions.BitmapScalingMode="HighQuality"
Source="/Images/app.png" />
<TextBlock
Grid.Column="1"
@ -633,8 +614,10 @@
<ScrollViewer
Margin="0,0,0,0"
Background="{DynamicResource Color01B}"
ScrollViewer.CanContentScroll="False">
<StackPanel Margin="5,18,25,30" Orientation="Vertical">
CanContentScroll="False"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.IsVirtualizing="True">
<VirtualizingStackPanel Margin="5,18,25,30" Orientation="Vertical">
<TextBlock
Grid.Row="2"
Margin="0,5,0,5"
@ -941,7 +924,7 @@
</TextBlock>
</ItemsControl>
</Border>
</StackPanel>
</VirtualizingStackPanel>
</ScrollViewer>
</TabItem>
<TabItem KeyDown="OnPluginSettingKeydown" Style="{DynamicResource NavTabItem}">
@ -1044,14 +1027,17 @@
Background="{DynamicResource Color01B}"
ItemContainerStyle="{StaticResource PluginList}"
ItemsSource="{Binding PluginViewModels}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.CanContentScroll="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedPlugin}"
SnapsToDevicePixels="True"
Style="{DynamicResource PluginListStyle}">
Style="{DynamicResource PluginListStyle}"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="0,0,0,18" />
<VirtualizingStackPanel Margin="0,0,0,18" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
@ -1401,10 +1387,12 @@
Text="{DynamicResource pluginStore}"
TextAlignment="left" />
</Border>
<DockPanel
Grid.Row="0"
Grid.Column="1"
Margin="5,24,0,0">
<TextBox
Name="pluginStoreFilterTxb"
Width="150"
@ -1456,290 +1444,223 @@
Padding="12,4,12,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnPluginStoreRefreshClick"
Command="{Binding RefreshExternalPluginsCommand}"
Content="{DynamicResource refresh}"
DockPanel.Dock="Right"
FontSize="13" />
</DockPanel>
<Border
<ListView
x:Name="StoreListBox"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Padding="0,0,0,0">
<ListBox
x:Name="StoreListBox"
Width="Auto"
Margin="5,1,0,0"
Padding="0,0,0,0"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
Background="{DynamicResource Color01B}"
ItemContainerStyle="{StaticResource StoreList}"
ItemsSource="{Binding ExternalPlugins}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Single"
Style="{DynamicResource StoreListStyle}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
VerticalAlignment="Top"
FontSize="16"
FontWeight="Bold"
Text="{Binding Name, Converter={StaticResource TextConverter}}" />
<ItemsPresenter />
</StackPanel>
Margin="4,0,0,0"
Padding="0,0,18,0"
ItemContainerStyle="{StaticResource StoreList}"
ItemsSource="{Binding Source={StaticResource PluginStoreCollectionView}}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Single"
Style="{DynamicResource StoreListStyle}"
VirtualizingPanel.CacheLength="1,1"
VirtualizingPanel.CacheLengthUnit="Page"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingPanel.VirtualizationMode="Standard">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<wpftk:VirtualizingWrapPanel
x:Name="ItemWrapPanel"
Margin="0,0,0,10"
ItemSize="216,184"
MouseWheelDelta="48"
Orientation="Vertical"
ScrollLineDelta="16"
SpacingMode="None"
StretchItems="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="2,0,0,10"
VerticalAlignment="Top"
FontSize="16"
FontWeight="Bold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name, Converter={StaticResource TextConverter}}" />
<ItemsPresenter />
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="{Binding Orientation, Mode=OneWay}" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
Margin="0,0,17,18"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Columns="3"
IsItemsHost="True"
SnapsToDevicePixels="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Hover Layout Style -->
<ListView.ItemTemplate>
<DataTemplate>
<!-- Hover Layout Style -->
<Button
Name="StoreListItem"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BorderThickness="0"
Click="StoreListItem_Click"
FocusVisualStyle="{StaticResource StoreItemFocusVisualStyleKey}">
<ui:FlyoutService.Flyout>
<ui:Flyout x:Name="InstallFlyout" Placement="Bottom">
<Grid MinWidth="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<VirtualizingStackPanel
Grid.Row="0"
Grid.Column="0"
Margin="5,0,0,0"
Orientation="Horizontal">
<TextBlock
Margin="0,0,5,0"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name}"
TextWrapping="Wrap"
ToolTip="{Binding Name}" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource Color05B}"
Text="{Binding Version}"
TextWrapping="Wrap"
ToolTip="{Binding Version}" />
</VirtualizingStackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="5,4,0,0"
TextWrapping="Wrap">
<Hyperlink
Foreground="{DynamicResource Color04B}"
NavigateUri="{Binding Website}"
RequestNavigate="OnRequestNavigate">
<Run FontSize="12" Text="{Binding Author, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
<ToggleButton
Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
HorizontalAlignment="Left"
HorizontalContentAlignment="left"
Background="Transparent"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Style="{StaticResource StoreItem}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<ContentPresenter
x:Name="contentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Focusable="False"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<ControlTemplate.Triggers />
</ControlTemplate>
</ToggleButton.Template>
<Grid
Height="160"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="56" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,2,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<VirtualizingStackPanel
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="20,0,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
MinHeight="42"
Margin="5,0,5,0"
Padding="15,5,15,5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Click="OnExternalPluginInstallClick"
Content="{DynamicResource installbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter='!'}" />
<Button
MinHeight="42"
Margin="5,0,5,0"
Padding="15,5,15,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnExternalPluginUninstallClick"
Content="{DynamicResource uninstallbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button
MinHeight="42"
Margin="5,0,5,0"
Padding="15,5,15,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnExternalPluginUpdateClick"
Content="{DynamicResource updatebtn}"
Style="{DynamicResource AccentButtonStyle}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
</VirtualizingStackPanel>
</Grid>
</ui:Flyout>
</ui:FlyoutService.Flyout>
<Grid>
<StackPanel Width="200">
<StackPanel Orientation="Horizontal">
<Image
Grid.Column="0"
Width="32"
Height="32"
Margin="18,24,0,0"
HorizontalAlignment="Left"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding IcoPath, IsAsync=True}" />
<Border
x:Name="LabelUpdate"
Height="12"
Margin="0,10,8,0"
Margin="10,24,0,0"
Padding="6,2,6,2"
HorizontalAlignment="Right"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#45BD59"
CornerRadius="36"
ToolTip="{DynamicResource LabelUpdateToolTip}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<StackPanel
Grid.Row="0"
Margin="0,0,0,0"
VerticalAlignment="Top">
<StackPanel.Style>
<Style>
<Setter Property="StackPanel.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode=TwoWay, Path=IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" Value="true">
<Setter Property="StackPanel.Opacity" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Image
Width="32"
Height="32"
Margin="20,20,6,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{Binding IcoPath, IsAsync=True}"
Stretch="Uniform" />
</StackPanel>
<StackPanel
Grid.Row="1"
Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Margin="0,6,0,0"
VerticalAlignment="Top"
Panel.ZIndex="0">
<StackPanel.Style>
<Style>
<Setter Property="StackPanel.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode=TwoWay, Path=IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" Value="true">
<Setter Property="StackPanel.Opacity" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock
Padding="20,0,20,0"
VerticalAlignment="Top"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name}"
TextWrapping="WrapWithOverflow"
ToolTip="{Binding Version}" />
<TextBlock
Margin="0,6,0,0"
Padding="20,0,22,20"
Foreground="{DynamicResource Color04B}"
TextWrapping="WrapWithOverflow">
<Run
FontSize="12"
Foreground="{DynamicResource Color04B}"
Text="{Binding Description, Mode=OneWay}" />
</TextBlock>
</StackPanel>
<StackPanel
Grid.RowSpan="2"
<TextBlock
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel.Style>
<Style>
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode=TwoWay, Path=IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" Value="true">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
Margin="18,10,18,0"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name}"
TextWrapping="Wrap"
ToolTip="{Binding Version}" />
<TextBlock
Grid.Column="0"
Height="60"
Margin="18,6,18,0"
Padding="0,0,0,10"
FontSize="12"
Foreground="{DynamicResource Color04B}"
Text="{Binding Description, Mode=OneWay}"
TextTrimming="WordEllipsis"
TextWrapping="Wrap" />
</StackPanel>
<Grid
Height="180"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Panel.ZIndex="1">
<Grid.Background>
<SolidColorBrush Opacity=".95" Color="{DynamicResource HoverStoreGrid}" />
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<StackPanel Orientation="Vertical">
<TextBlock
Margin="20,20,20,0"
Padding="0,0,0,0"
FontWeight="Bold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name}"
TextWrapping="WrapWithOverflow"
ToolTip="{Binding Name}" />
<TextBlock
Margin="20,4,20,0"
Padding="0,0,20,0"
Foreground="{DynamicResource Color05B}"
Text="{Binding Version}"
TextWrapping="WrapWithOverflow"
ToolTip="{Binding Version}" />
</StackPanel>
<StackPanel Margin="20,6,20,0" Orientation="Vertical">
<TextBlock Padding="0,0,0,0" TextWrapping="Wrap">
<Hyperlink
Foreground="{DynamicResource Color04B}"
NavigateUri="{Binding Website}"
RequestNavigate="OnRequestNavigate">
<Run FontSize="12" Text="{Binding Author, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</StackPanel>
<Grid Grid.Row="0" Grid.Column="1">
<StackPanel
Margin="0,70,20,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
MinHeight="40"
Padding="15,5,15,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnExternalPluginInstallClick"
Content="{DynamicResource installbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter='!'}" />
<Button
MinHeight="40"
Margin="5,0,0,0"
Padding="15,5,15,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnExternalPluginUninstallClick"
Content="{DynamicResource uninstallbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button
MinHeight="40"
Margin="5,0,0,0"
Padding="15,5,15,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnExternalPluginUpdateClick"
Content="{DynamicResource updatebtn}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
<!-- Hide Install Button when installed Item -->
</StackPanel>
</Grid>
</Grid>
</StackPanel>
</Grid>
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</TabItem>
<!--#endregion-->
@ -1763,7 +1684,9 @@
<ScrollViewer
Margin="0,0,0,0"
Padding="6,0,24,0"
ScrollViewer.CanContentScroll="False">
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<Grid Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="0" />
@ -2339,16 +2262,26 @@
<ScrollViewer
Margin="0,0,0,0"
Padding="0,0,6,0"
ScrollViewer.CanContentScroll="False">
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<Border>
<Grid Margin="5,18,18,10">
<Grid.RowDefinitions>
<RowDefinition Height="43" />
<RowDefinition Height="80" />
<RowDefinition Height="146" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
@ -2453,6 +2386,7 @@
Text="{DynamicResource customQueryHotkey}" />
<ListView
Grid.Row="4"
MinHeight="160"
Margin="0,0,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
@ -2493,7 +2427,7 @@
<Button
MinWidth="100"
Margin="10"
Click="OnnEditCustomHotkeyClick"
Click="OnEditCustomHotkeyClick"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
@ -2501,6 +2435,104 @@
Click="OnAddCustomHotkeyClick"
Content="{DynamicResource add}" />
</StackPanel>
<TextBlock
Grid.Row="6"
Margin="0,0,12,2"
Padding="0,12,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource customQueryShortcut}" />
<ListView
Name="customShortcutView"
Grid.Row="7"
MinHeight="160"
Margin="0,6,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding CustomShortcuts}"
SelectedItem="{Binding SelectedCustomShortcut}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="540" Header="{DynamicResource customShortcutExpansion}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel
Grid.Row="8"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Click="OnDeleteCustomShortCutClick"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Click="OnEditCustomShortCutClick"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10,10,0,10"
Click="OnAddCustomShortCutClick"
Content="{DynamicResource add}" />
</StackPanel>
<TextBlock
Grid.Row="9"
Margin="0,0,12,2"
Padding="0,12,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color05B}"
Text="Built-in Shortcuts" />
<ListView
Grid.Row="10"
MinHeight="160"
Margin="0,6,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding BuiltinShortcuts}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="540" Header="{DynamicResource builtinShortcutDescription}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description, Converter={StaticResource TranlationConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Border>
</ScrollViewer>
@ -2526,7 +2558,9 @@
<ScrollViewer
Margin="0,0,0,0"
Padding="5,0,24,0"
ScrollViewer.CanContentScroll="False">
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<Border>
<StackPanel>
@ -2698,7 +2732,9 @@
<ScrollViewer
Margin="0,0,0,0"
Background="{DynamicResource Color01B}"
ScrollViewer.CanContentScroll="False">
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel Margin="5,14,25,30" Orientation="Vertical">
<TextBlock
Grid.Row="2"

View file

@ -8,14 +8,19 @@ using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using ModernWpf;
using ModernWpf.Controls;
using System;
using System.Drawing.Printing;
using System.IO;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Navigation;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
using Button = System.Windows.Controls.Button;
using Control = System.Windows.Controls.Control;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
@ -39,10 +44,6 @@ namespace Flow.Launcher
API = api;
InitializePosition();
InitializeComponent();
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("Category");
view.GroupDescriptions.Add(groupDescription);
}
#region General
@ -154,7 +155,7 @@ namespace Flow.Launcher
}
}
private void OnnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
{
var item = viewModel.SelectedCustomPluginHotkey;
if (item != null)
@ -296,17 +297,35 @@ namespace Flow.Launcher
}
}
private void OnPluginStoreRefreshClick(object sender, RoutedEventArgs e)
private static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
_ = viewModel.RefreshExternalPluginsAsync();
}
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button)
{
viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
return;
}
if (storeClickedButton != null)
{
FlyoutService.GetFlyout(storeClickedButton).Hide();
}
viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
}
private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e)
@ -317,18 +336,30 @@ namespace Flow.Launcher
viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
}
}
private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e)
{
if (storeClickedButton != null)
{
FlyoutService.GetFlyout(storeClickedButton).Hide();
}
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
}
private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e)
{
if (storeClickedButton != null)
{
FlyoutService.GetFlyout(storeClickedButton).Hide();
}
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
}
private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
@ -381,11 +412,34 @@ namespace Flow.Launcher
restoreButton.Visibility = Visibility.Collapsed;
}
}
private void Window_StateChanged(object sender, EventArgs e)
{
RefreshMaximizeRestoreButton();
}
#region Shortcut
private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e)
{
viewModel.DeleteSelectedCustomShortcut();
}
private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e)
{
if (viewModel.EditSelectedCustomShortcut())
{
customShortcutView.Items.Refresh();
}
}
private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e)
{
viewModel.AddCustomShortcut();
}
#endregion
private CollectionView pluginListView;
private CollectionView pluginStoreView;
@ -463,6 +517,7 @@ namespace Flow.Launcher
{
ClockDisplay();
}
public void ClockDisplay()
{
if (settings.UseClock)
@ -517,5 +572,21 @@ namespace Flow.Launcher
return top;
}
private Button storeClickedButton;
private void StoreListItem_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button button)
return;
storeClickedButton = button;
var flyout = FlyoutService.GetFlyout(button);
flyout.Closed += (_, _) =>
{
storeClickedButton = null;
};
}
}
}

View file

@ -17,6 +17,7 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Storage;
using Flow.Launcher.Infrastructure.Logger;
using Microsoft.VisualStudio.Threading;
using System.Text;
using System.Threading.Channels;
using ISavable = Flow.Launcher.Plugin.ISavable;
using System.IO;
@ -621,7 +622,9 @@ namespace Flow.Launcher.ViewModel
{
_updateSource?.Cancel();
if (string.IsNullOrWhiteSpace(QueryText))
var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
if (query == null) // shortcut expanded
{
Results.Clear();
Results.Visbility = Visibility.Collapsed;
@ -645,8 +648,7 @@ namespace Flow.Launcher.ViewModel
if (currentCancellationToken.IsCancellationRequested)
return;
var query = QueryBuilder.Build(QueryText, PluginManager.NonGlobalPlugins);
// handle the exclusiveness of plugin using action keyword
RemoveOldQueryResults(query);
@ -736,6 +738,40 @@ namespace Flow.Launcher.ViewModel
}
}
private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts, IEnumerable<BuiltinShortcutModel> builtInShortcuts)
{
if (string.IsNullOrWhiteSpace(queryText))
{
return null;
}
StringBuilder queryBuilder = new(queryText);
StringBuilder queryBuilderTmp = new(queryText);
foreach (var shortcut in customShortcuts)
{
if (queryBuilder.Equals(shortcut.Key))
{
queryBuilder.Replace(shortcut.Key, shortcut.Expand());
}
queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
}
foreach (var shortcut in builtInShortcuts)
{
queryBuilder.Replace(shortcut.Key, shortcut.Expand());
queryBuilderTmp.Replace(shortcut.Key, shortcut.Expand());
}
// show expanded builtin shortcuts
// use private field to avoid infinite recursion
_queryText = queryBuilderTmp.ToString();
var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
return query;
}
private void RemoveOldQueryResults(Query query)
{
if (_lastQuery?.ActionKeyword != query?.ActionKeyword)

View file

@ -19,10 +19,12 @@ using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Input;
namespace Flow.Launcher.ViewModel
{
public class SettingWindowViewModel : BaseModel
public partial class SettingWindowViewModel : BaseModel
{
private readonly Updater _updater;
private readonly IPortable _portable;
@ -212,11 +214,19 @@ namespace Flow.Launcher.ViewModel
}
}
public List<string> OpenResultModifiersList => new List<string> { KeyConstant.Alt, KeyConstant.Ctrl, $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" };
public List<string> OpenResultModifiersList => new List<string>
{
KeyConstant.Alt,
KeyConstant.Ctrl,
$"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
};
private Internationalization _translater => InternationalizationManager.Instance;
public List<Language> Languages => _translater.LoadAvailableLanguages();
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
public ObservableCollection<CustomShortcutModel> CustomShortcuts => Settings.CustomShortcuts;
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts => Settings.BuiltinShortcuts;
public string TestProxy()
{
var proxyServer = Settings.Proxy.Server;
@ -275,7 +285,10 @@ namespace Flow.Launcher.ViewModel
var metadatas = PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel { PluginPair = p })
.Select(p => new PluginViewModel
{
PluginPair = p
})
.ToList();
return metadatas;
}
@ -318,7 +331,8 @@ namespace Flow.Launcher.ViewModel
}
}
public async Task RefreshExternalPluginsAsync()
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
await PluginsManifest.UpdateManifestAsync();
OnPropertyChanged(nameof(ExternalPlugins));
@ -397,7 +411,11 @@ namespace Flow.Launcher.ViewModel
{
var key = $"ColorScheme{e}";
var display = _translater.GetTranslation(key);
var m = new ColorScheme { Display = display, Value = e, };
var m = new ColorScheme
{
Display = display,
Value = e,
};
modes.Add(m);
}
return modes;
@ -519,8 +537,13 @@ namespace Flow.Launcher.ViewModel
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = memStream;
bitmap.DecodePixelWidth = 800;
bitmap.DecodePixelHeight = 600;
bitmap.EndInit();
var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
var brush = new ImageBrush(bitmap)
{
Stretch = Stretch.UniformToFill
};
return brush;
}
else
@ -548,19 +571,19 @@ namespace Flow.Launcher.ViewModel
{
Title = "WebSearch",
SubTitle = "Search the web with different search engine support",
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
},
new Result
{
Title = "Program",
SubTitle = "Launch programs as admin or a different user",
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
},
new Result
{
Title = "ProcessKiller",
SubTitle = "Terminate unwanted processes",
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
}
};
var vm = new ResultsViewModel(Settings);
@ -574,8 +597,8 @@ namespace Flow.Launcher.ViewModel
get
{
if (Fonts.SystemFontFamilies.Count(o =>
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
{
var font = new FontFamily(Settings.QueryBoxFont);
return font;
@ -602,7 +625,7 @@ namespace Flow.Launcher.ViewModel
Settings.QueryBoxFontStyle,
Settings.QueryBoxFontWeight,
Settings.QueryBoxFontStretch
));
));
return typeface;
}
set
@ -619,8 +642,8 @@ namespace Flow.Launcher.ViewModel
get
{
if (Fonts.SystemFontFamilies.Count(o =>
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
{
var font = new FontFamily(Settings.ResultFont);
return font;
@ -647,7 +670,7 @@ namespace Flow.Launcher.ViewModel
Settings.ResultFontStyle,
Settings.ResultFontWeight,
Settings.ResultFontStretch
));
));
return typeface;
}
set
@ -669,6 +692,65 @@ namespace Flow.Launcher.ViewModel
#endregion
#region shortcut
public CustomShortcutModel? SelectedCustomShortcut { get; set; }
public void DeleteSelectedCustomShortcut()
{
var item = SelectedCustomShortcut;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
string deleteWarning = string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"),
item?.Key, item?.Value);
if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Settings.CustomShortcuts.Remove(item);
}
}
public bool EditSelectedCustomShortcut()
{
var item = SelectedCustomShortcut;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return false;
}
var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this);
if (shortcutSettingWindow.ShowDialog() == true)
{
item.Key = shortcutSettingWindow.Key;
item.Value = shortcutSettingWindow.Value;
return true;
}
return false;
}
public void AddCustomShortcut()
{
var shortcutSettingWindow = new CustomShortcutSetting(this);
if (shortcutSettingWindow.ShowDialog() == true)
{
var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value);
Settings.CustomShortcuts.Add(shortcut);
}
}
public bool ShortcutExists(string key)
{
return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key);
}
#endregion
#region about
public string Website => Constant.Website;

View file

@ -601,92 +601,97 @@ namespace Flow.Launcher.Plugin.Program.Programs
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
string path;
if (uri.Contains("\\"))
{
path = Path.Combine(Package.Location, uri);
}
else
string path = Path.Combine(Package.Location, uri);
var logoPath = TryToFindLogo(uri, path);
if (String.IsNullOrEmpty(logoPath))
{
// TODO: Don't know why, just keep it at the moment
// Maybe on older version of Windows 10?
// for C:\Windows\MiracastView etc
path = Path.Combine(Package.Location, "Assets", uri);
return TryToFindLogo(uri, Path.Combine(Package.Location, "Assets", uri));
}
return logoPath;
var extension = Path.GetExtension(path);
if (extension != null)
string TryToFindLogo(string uri, string path)
{
var end = path.Length - extension.Length;
var prefix = path.Substring(0, end);
var paths = new List<string>
var extension = Path.GetExtension(path);
if (extension != null)
{
path
};
//if (File.Exists(path))
//{
// return path; // shortcut, avoid enumerating files
//}
var scaleFactors = new Dictionary<PackageVersion, List<int>>
{
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
if (String.IsNullOrEmpty(logoNamePrefix) || String.IsNullOrEmpty(logoDir) || !Directory.Exists(logoDir))
{
PackageVersion.Windows10, new List<int>
{
100,
125,
150,
200,
400
}
},
// Known issue: Edge always triggers it since logo is not at uri
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Package.Location}", new FileNotFoundException());
return string.Empty;
}
var files = Directory.EnumerateFiles(logoDir);
// Currently we don't care which one to choose
// Just ignore all qualifiers
// select like logo.[xxx_yyy].png
// https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
var logos = files.Where(file =>
Path.GetFileName(file)?.StartsWith(logoNamePrefix, StringComparison.OrdinalIgnoreCase) ?? false
&& extension.Equals(Path.GetExtension(file), StringComparison.OrdinalIgnoreCase)
);
var selected = logos.FirstOrDefault();
var closest = selected;
int min = int.MaxValue;
foreach(var logo in logos)
{
PackageVersion.Windows81, new List<int>
var imageStream = File.OpenRead(logo);
var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
var height = decoder.Frames[0].PixelHeight;
var width = decoder.Frames[0].PixelWidth;
int pixelCountDiff = Math.Abs(height * width - 1936); // 44*44=1936
if(pixelCountDiff < min)
{
100,
120,
140,
160,
180
}
},
{
PackageVersion.Windows8, new List<int>
{
100
// try to find the closest to 44x44 logo
closest = logo;
if (pixelCountDiff == 0)
break; // found 44x44
min = pixelCountDiff;
}
}
};
if (scaleFactors.ContainsKey(Package.Version))
{
foreach (var factor in scaleFactors[Package.Version])
selected = closest;
if (!string.IsNullOrEmpty(selected))
{
paths.Add($"{prefix}.scale-{factor}{extension}");
return selected;
}
else
{
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
var selected = paths.FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selected))
{
return selected;
}
else
{
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
$"|Unable to find extension from {uri} for {UserModelId} " +
$"in package location {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
else
{
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
$"|Unable to find extension from {uri} for {UserModelId} " +
$"in package location {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
public ImageSource Logo()
{
var logo = ImageFromPath(LogoPath);
var plated = PlatedImage(logo);
var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
// todo magic! temp fix for cross thread object
plated.Freeze();