- Merge Split Setting

This commit is contained in:
DB p 2024-05-23 22:50:58 +09:00
commit 215f5ac4e2
61 changed files with 6478 additions and 5077 deletions

View file

@ -1,4 +1,4 @@
using System; using System;
namespace Flow.Launcher.Core.ExternalPlugins namespace Flow.Launcher.Core.ExternalPlugins
{ {
@ -13,9 +13,11 @@ namespace Flow.Launcher.Core.ExternalPlugins
public string Website { get; set; } public string Website { get; set; }
public string UrlDownload { get; set; } public string UrlDownload { get; set; }
public string UrlSourceCode { get; set; } public string UrlSourceCode { get; set; }
public string LocalInstallPath { get; set; }
public string IcoPath { get; set; } public string IcoPath { get; set; }
public DateTime? LatestReleaseDate { get; set; } public DateTime? LatestReleaseDate { get; set; }
public DateTime? DateAdded { get; set; } public DateTime? DateAdded { get; set; }
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
} }
} }

View file

@ -380,7 +380,8 @@ namespace Flow.Launcher.Core.Plugin
/// <summary> /// <summary>
/// Update a plugin to new version, from a zip file. Will Delete zip after updating. /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
/// unless it's a local path installation
/// </summary> /// </summary>
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{ {
@ -390,11 +391,11 @@ namespace Flow.Launcher.Core.Plugin
} }
/// <summary> /// <summary>
/// Install a plugin. Will Delete zip after updating. /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation
/// </summary> /// </summary>
public static void InstallPlugin(UserPlugin plugin, string zipFilePath) public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{ {
InstallPlugin(plugin, zipFilePath, true); InstallPlugin(plugin, zipFilePath, checkModified: true);
} }
/// <summary> /// <summary>
@ -420,7 +421,9 @@ namespace Flow.Launcher.Core.Plugin
// Unzip plugin files to temp folder // Unzip plugin files to temp folder
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
File.Delete(zipFilePath);
if(!plugin.IsFromLocalInstallPath)
File.Delete(zipFilePath);
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);

View file

@ -31,6 +31,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public string SelectPrevPageHotkey { get; set; } = $"PageDown"; public string SelectPrevPageHotkey { get; set; } = $"PageDown";
public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O";
public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; public string SettingWindowHotkey { get; set; } = $"Ctrl+I";
public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up";
public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down";
public string Language public string Language
{ {
@ -283,42 +285,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{ {
get get
{ {
var list = new List<RegisteredHotkeyData> var list = FixedHotkeys();
{
new("Up", "HotkeyLeftRightDesc"),
new("Down", "HotkeyLeftRightDesc"),
new("Left", "HotkeyUpDownDesc"),
new("Right", "HotkeyUpDownDesc"),
new("Escape", "HotkeyESCDesc"),
new("F5", "ReloadPluginHotkey"),
new("Alt+Home", "HotkeySelectFirstResult"),
new("Alt+End", "HotkeySelectLastResult"),
new("Ctrl+R", "HotkeyRequery"),
new("Ctrl+H", "ToggleHistoryHotkey"),
new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"),
new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"),
new("Ctrl+OemPlus", "QuickHeightHotkey"),
new("Ctrl+OemMinus", "QuickHeightHotkey"),
new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"),
new("Shift+Enter", "OpenContextMenuHotkey"),
new("Enter", "HotkeyRunDesc"),
new("Ctrl+Enter", "OpenContainFolderHotkey"),
new("Alt+Enter", "HotkeyOpenResult"),
new("Ctrl+F12", "ToggleGameModeHotkey"),
new("Ctrl+Shift+C", "CopyFilePathHotkey"),
new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1),
new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2),
new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3),
new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4),
new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5),
new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6),
new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7),
new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8),
new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9),
new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10)
};
// Customizeable hotkeys
if(!string.IsNullOrEmpty(Hotkey)) if(!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if(!string.IsNullOrEmpty(PreviewHotkey)) if(!string.IsNullOrEmpty(PreviewHotkey))
@ -343,7 +312,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryUpHotkey))
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
// Custom Query Hotkeys
foreach (var customPluginHotkey in CustomPluginHotkeys) foreach (var customPluginHotkey in CustomPluginHotkeys)
{ {
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
@ -353,6 +327,45 @@ namespace Flow.Launcher.Infrastructure.UserSettings
return list; return list;
} }
} }
private List<RegisteredHotkeyData> FixedHotkeys()
{
return new List<RegisteredHotkeyData>
{
new("Up", "HotkeyLeftRightDesc"),
new("Down", "HotkeyLeftRightDesc"),
new("Left", "HotkeyUpDownDesc"),
new("Right", "HotkeyUpDownDesc"),
new("Escape", "HotkeyESCDesc"),
new("F5", "ReloadPluginHotkey"),
new("Alt+Home", "HotkeySelectFirstResult"),
new("Alt+End", "HotkeySelectLastResult"),
new("Ctrl+R", "HotkeyRequery"),
new("Ctrl+H", "ToggleHistoryHotkey"),
new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"),
new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"),
new("Ctrl+OemPlus", "QuickHeightHotkey"),
new("Ctrl+OemMinus", "QuickHeightHotkey"),
new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"),
new("Shift+Enter", "OpenContextMenuHotkey"),
new("Enter", "HotkeyRunDesc"),
new("Ctrl+Enter", "OpenContainFolderHotkey"),
new("Alt+Enter", "HotkeyOpenResult"),
new("Ctrl+F12", "ToggleGameModeHotkey"),
new("Ctrl+Shift+C", "CopyFilePathHotkey"),
new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1),
new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2),
new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3),
new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4),
new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5),
new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6),
new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7),
new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8),
new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9),
new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10)
};
}
} }
public enum LastQueryMode public enum LastQueryMode

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
#pragma warning disable IDE0005 #pragma warning disable IDE0005
using System.Windows; using System.Windows;
#pragma warning restore IDE0005 #pragma warning restore IDE0005
@ -200,6 +201,24 @@ namespace Flow.Launcher.Plugin.SharedCommands
} }
} }
///<summary>
/// This checks whether a given string is a zip file path.
/// By default does not check if the zip file actually exist on disk, can do so by
/// setting checkFileExists = true.
///</summary>
public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false)
{
if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip")
{
if (checkFileExists)
return FileExists(querySearchString);
return true;
}
return false;
}
///<summary> ///<summary>
/// This checks whether a given string is a directory path or network location string. /// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists. /// It does not check if location actually exists.

View file

@ -191,11 +191,15 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"\c:\", false)] [TestCase(@"\c:\", false)]
[TestCase(@"cc:\", false)] [TestCase(@"cc:\", false)]
[TestCase(@"\\\SomeNetworkLocation\", false)] [TestCase(@"\\\SomeNetworkLocation\", false)]
[TestCase(@"\\SomeNetworkLocation\", true)]
[TestCase("RandomFile", false)] [TestCase("RandomFile", false)]
[TestCase(@"c:\>*", true)] [TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", true)] [TestCase(@"c:\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)]
[TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)]
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
{ {
// When, Given // When, Given

View file

@ -24,6 +24,7 @@
</ui:ThemeResources> </ui:ThemeResources>
<ui:XamlControlsResources /> <ui:XamlControlsResources />
<ResourceDictionary Source="pack://application:,,,/Resources/CustomControlTemplate.xaml" /> <ResourceDictionary Source="pack://application:,,,/Resources/CustomControlTemplate.xaml" />
<ResourceDictionary Source="pack://application:,,,/Resources/SettingWindowStyle.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/Win11System.xaml" /> <ResourceDictionary Source="pack://application:,,,/Themes/Win11System.xaml" />
<ResourceDictionary Source="pack://application:,,,/Languages/en.xaml" /> <ResourceDictionary Source="pack://application:,,,/Languages/en.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>

View file

@ -8,7 +8,6 @@ namespace Flow.Launcher
{ {
public partial class CustomShortcutSetting : Window public partial class CustomShortcutSetting : Window
{ {
private SettingWindowViewModel viewModel;
public string Key { get; set; } = String.Empty; public string Key { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty; public string Value { get; set; } = String.Empty;
private string originalKey { get; init; } = null; private string originalKey { get; init; } = null;
@ -17,13 +16,11 @@ namespace Flow.Launcher
public CustomShortcutSetting(SettingWindowViewModel vm) public CustomShortcutSetting(SettingWindowViewModel vm)
{ {
viewModel = vm;
InitializeComponent(); InitializeComponent();
} }
public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm) public CustomShortcutSetting(string key, string value)
{ {
viewModel = vm;
Key = key; Key = key;
Value = value; Value = value;
originalKey = key; originalKey = key;
@ -46,8 +43,7 @@ namespace Flow.Launcher
return; return;
} }
// Check if key is modified or adding a new one // Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update) if ((update && originalKey != Key) || !update)
&& viewModel.ShortcutExists(Key))
{ {
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut")); MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
return; return;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -189,6 +189,8 @@
<system:String x:Key="SelectPrevItemHotkey">Select Previous Item</system:String> <system:String x:Key="SelectPrevItemHotkey">Select Previous Item</system:String>
<system:String x:Key="SelectNextPageHotkey">Next Page</system:String> <system:String x:Key="SelectNextPageHotkey">Next Page</system:String>
<system:String x:Key="SelectPrevPageHotkey">Previous Page</system:String> <system:String x:Key="SelectPrevPageHotkey">Previous Page</system:String>
<system:String x:Key="CycleHistoryUpHotkey">Cycle Previous Query</system:String>
<system:String x:Key="CycleHistoryDownHotkey">Cycle Next Query</system:String>
<system:String x:Key="OpenContextMenuHotkey">Open Context Menu</system:String> <system:String x:Key="OpenContextMenuHotkey">Open Context Menu</system:String>
<system:String x:Key="SettingWindowHotkey">Open Setting Window</system:String> <system:String x:Key="SettingWindowHotkey">Open Setting Window</system:String>
<system:String x:Key="CopyFilePathHotkey">Copy File Path</system:String> <system:String x:Key="CopyFilePathHotkey">Copy File Path</system:String>

View file

@ -198,6 +198,14 @@
Key="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Key="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectPrevPageCommand}" Command="{Binding SelectPrevPageCommand}"
Modifiers="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> Modifiers="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding CycleHistoryUpHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding ReverseHistoryCommand}"
Modifiers="{Binding CycleHistoryUpHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding ForwardHistoryCommand}"
Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings> </Window.InputBindings>
<Grid> <Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}"> <Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">

View file

@ -20,6 +20,7 @@
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" /> <Setter Property="CornerRadius" Value="5" />
<Setter Property="MinHeight" Value="68" /> <Setter Property="MinHeight" Value="68" />
<Setter Property="Padding" Value="0,15,0,15" />
<Setter Property="Margin" Value="0,4,0,0" /> <Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="SnapsToDevicePixels" Value="True" />
<Style.Triggers> <Style.Triggers>
@ -36,6 +37,27 @@
<Setter Property="Padding" Value="38,0,26,0" /> <Setter Property="Padding" Value="38,0,26,0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</DataTrigger> </DataTrigger>
<DataTrigger Binding="{Binding (local:CardGroup.Position), RelativeSource={RelativeSource AncestorType=local:Card}}" Value="First">
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</DataTrigger>
<DataTrigger Binding="{Binding (local:CardGroup.Position), RelativeSource={RelativeSource AncestorType=local:Card}}" Value="Middle">
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0 1 0 0" />
</DataTrigger>
<DataTrigger Binding="{Binding (local:CardGroup.Position), RelativeSource={RelativeSource AncestorType=local:Card}}" Value="Last">
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0 1 0 0" />
</DataTrigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
</Border.Style> </Border.Style>

View file

@ -16,6 +16,7 @@ namespace Flow.Launcher.Resources.Controls
{ {
InitializeComponent(); InitializeComponent();
} }
public string Title public string Title
{ {
get { return (string)GetValue(TitleProperty); } get { return (string)GetValue(TitleProperty); }

View file

@ -0,0 +1,32 @@
<UserControl x:Class="Flow.Launcher.Resources.Controls.CardGroup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance cc:CardGroup}"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="cc:Card" x:Key="FirstStyle">
<Setter Property="cc:CardGroup.Position" Value="First" />
</Style>
<Style TargetType="cc:Card" x:Key="MiddleStyle">
<Setter Property="cc:CardGroup.Position" Value="Middle" />
</Style>
<Style TargetType="cc:Card" x:Key="LastStyle">
<Setter Property="cc:CardGroup.Position" Value="Last" />
</Style>
<cc:CardGroupCardStyleSelector
x:Key="CardStyleSelector"
FirstStyle="{StaticResource FirstStyle}"
MiddleStyle="{StaticResource MiddleStyle}"
LastStyle="{StaticResource LastStyle}" />
</UserControl.Resources>
<Border Background="{DynamicResource Color00B}" BorderBrush="{DynamicResource Color03B}" BorderThickness="1"
CornerRadius="5">
<ItemsControl ItemsSource="{Binding Content, RelativeSource={RelativeSource AncestorType=cc:CardGroup}}"
ItemContainerStyleSelector="{StaticResource CardStyleSelector}" />
</Border>
</UserControl>

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Resources.Controls;
public partial class CardGroup : UserControl
{
public enum CardGroupPosition
{
NotInGroup,
First,
Middle,
Last
}
public new ObservableCollection<Card> Content
{
get { return (ObservableCollection<Card>)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static new readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(ObservableCollection<Card>), typeof(CardGroup));
public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached(
"Position", typeof(CardGroupPosition), typeof(CardGroup),
new FrameworkPropertyMetadata(CardGroupPosition.NotInGroup, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetPosition(UIElement element, CardGroupPosition value)
{
element.SetValue(PositionProperty, value);
}
public static CardGroupPosition GetPosition(UIElement element)
{
return (CardGroupPosition)element.GetValue(PositionProperty);
}
public CardGroup()
{
InitializeComponent();
Content = new ObservableCollection<Card>();
}
}

View file

@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Resources.Controls;
public class CardGroupCardStyleSelector : StyleSelector
{
public Style FirstStyle { get; set; }
public Style MiddleStyle { get; set; }
public Style LastStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
var index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
if (index == 0) return FirstStyle;
if (index == itemsControl.Items.Count - 1) return LastStyle;
return MiddleStyle;
}
}

View file

@ -0,0 +1,14 @@
<UserControl x:Class="Flow.Launcher.Resources.Controls.HyperLink"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TextBlock>
<Hyperlink NavigateUri="{Binding Uri, RelativeSource={RelativeSource AncestorType=UserControl}}"
RequestNavigate="Hyperlink_OnRequestNavigate">
<Run Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Hyperlink>
</TextBlock>
</UserControl>

View file

@ -0,0 +1,39 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace Flow.Launcher.Resources.Controls;
public partial class HyperLink : UserControl
{
public static readonly DependencyProperty UriProperty = DependencyProperty.Register(
nameof(Uri), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string))
);
public string Uri
{
get => (string)GetValue(UriProperty);
set => SetValue(UriProperty, value);
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string))
);
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public HyperLink()
{
InitializeComponent();
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
App.API.OpenUrl(e.Uri);
e.Handled = true;
}
}

View file

@ -0,0 +1,224 @@
<UserControl x:Class="Flow.Launcher.Resources.Controls.InstalledPluginDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:viewModel="clr-namespace:Flow.Launcher.ViewModel"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:PluginViewModel}"
d:DesignHeight="300" d:DesignWidth="300">
<Expander
Padding="0"
BorderThickness="0"
ClipToBounds="True"
IsExpanded="{Binding Mode=TwoWay, Path=IsExpanded}"
SnapsToDevicePixels="True"
Style="{StaticResource ExpanderStyle1}">
<Expander.Header>
<Border Padding="0 12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36" MinWidth="36" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Width="32"
Height="32"
Source="{Binding Image, IsAsync=True}" />
<StackPanel Grid.Column="1" Margin="16 0 14 0">
<TextBlock
Foreground="{DynamicResource Color05B}"
Text="{Binding PluginPair.Metadata.Name}"
TextWrapping="Wrap"
ToolTip="{Binding PluginPair.Metadata.Version}" />
<TextBlock
Margin="0 2 0 0"
Foreground="{DynamicResource Color04B}"
FontSize="12"
Text="{Binding PluginPair.Metadata.Description}"
TextWrapping="WrapWithOverflow" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock
Margin="0 0 8 0"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource Color08B}"
Text="{DynamicResource priority}" />
<Button
x:Name="PriorityButton"
Margin="0 0 22 0"
VerticalAlignment="Center"
Command="{Binding EditPluginPriorityCommand}"
Content="{Binding Priority}"
Cursor="Hand"
ToolTip="{DynamicResource priorityToolTip}">
<!--#region Priority Button Style-->
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="2" />
</Style>
</Button.Resources>
<Button.Style>
<Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="Button">
<Setter Property="Padding" Value="12 8" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontWeight" Value="DemiBold" />
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=PriorityButton, UpdateSourceTrigger=PropertyChanged, Path=Content}"
Value="0">
<Setter Property="Foreground" Value="{DynamicResource Color08B}" />
<Setter Property="FontWeight" Value="Normal" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<!--#endregion-->
</Button>
<ui:ToggleSwitch
Margin="0 0 8 0"
IsOn="{Binding PluginState}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</StackPanel>
</Grid>
</Border>
</Expander.Header>
<StackPanel>
<Border
Width="Auto"
Height="52"
Margin="0"
Padding="0"
BorderThickness="0 1 0 0"
CornerRadius="0"
Style="{DynamicResource SettingGroupBox}"
Visibility="{Binding ActionKeywordsVisibility}">
<DockPanel Margin="22 0 18 0" VerticalAlignment="Center">
<TextBlock
Margin="48 0 10 0"
DockPanel.Dock="Left"
Style="{StaticResource Glyph}">
&#xe819;
</TextBlock>
<TextBlock
HorizontalAlignment="Left"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource actionKeywords}" />
<Button
Width="100"
Height="34"
Margin="5 0 0 0"
HorizontalAlignment="Right"
Command="{Binding SetActionKeywordsCommand}"
Content="{Binding ActionKeywordsText}"
Cursor="Hand"
DockPanel.Dock="Right"
FontWeight="Bold"
ToolTip="{DynamicResource actionKeywordsTooltip}"
Visibility="{Binding ActionKeywordsVisibility}" />
</DockPanel>
</Border>
<Border
BorderThickness="0 1 0 0"
BorderBrush="{DynamicResource Color03B}"
Background="{DynamicResource Color00B}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSettingControl}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentControl
Margin="0"
Padding="1"
VerticalAlignment="Stretch"
Content="{Binding SettingControl}" />
</Border>
<Border
Margin="0"
Padding="15 10"
VerticalAlignment="Center"
BorderThickness="0 1 0 0"
CornerRadius="0 0 5 5"
Style="{DynamicResource SettingGroupBox}">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock
Margin="10 0 0 0"
VerticalAlignment="center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="{DynamicResource author}" />
<TextBlock
Margin="5 0 0 0"
VerticalAlignment="center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="{Binding PluginPair.Metadata.Author}" />
<TextBlock
Margin="10 0 0 0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="|" />
<TextBlock
Margin="10 0 5 0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="{Binding Version}"
ToolTip="{Binding InitAndQueryTime}"
ToolTipService.InitialShowDelay="500" />
<TextBlock
Margin="5 0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource PluginInfoColor}"
Text="|" />
<TextBlock
Margin="5 0 0 0"
Style="{DynamicResource LinkBtnStyle}"
Text="&#xe80f;"
ToolTip="{DynamicResource plugin_query_web}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding OpenSourceCodeLinkCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
<TextBlock
Margin="10 0 0 0"
Style="{DynamicResource LinkBtnStyle}"
Text="&#xe74d;"
ToolTip="{DynamicResource plugin_uninstall}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding OpenDeletePluginWindowCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
<TextBlock
Margin="10 0 5 0"
Style="{DynamicResource LinkBtnStyle}"
Text="&#xe8b7;"
ToolTip="{DynamicResource pluginDirectory}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding OpenPluginDirectoryCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</Expander>
</UserControl>

View file

@ -0,0 +1,9 @@
namespace Flow.Launcher.Resources.Controls;
public partial class InstalledPluginDisplay
{
public InstalledPluginDisplay()
{
InitializeComponent();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace Flow.Launcher.Resources.MarkupExtensions;
#nullable enable
public class CollapsedWhenExtension : MarkupExtension {
private Binding? When { get; set; }
public object? IsEqualTo { get; set; }
public bool? IsEqualToBool
{
get => IsEqualTo switch
{
bool b => b,
_ => null
};
set => IsEqualTo = value;
}
public int? IsEqualToInt
{
get => IsEqualTo switch
{
int i => i,
_ => null
};
set => IsEqualTo = value;
}
protected virtual Visibility DefaultVisibility => Visibility.Visible;
protected virtual Visibility InvertedVisibility => Visibility.Collapsed;
public CollapsedWhenExtension(Binding when)
{
When = when;
}
public override object ProvideValue(IServiceProvider serviceProvider) {
if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideValueTarget provideValueTarget)
return DependencyProperty.UnsetValue;
if (provideValueTarget is not { TargetObject: not null, TargetProperty: not null })
return DependencyProperty.UnsetValue;
if (When is null)
return DependencyProperty.UnsetValue;
if (IsEqualTo is Binding isEqualToBinding)
{
var multiBinding = new MultiBinding
{
Converter = new HideableVisibilityConverter
{
DefaultVisibility = DefaultVisibility,
InvertedVisibility = InvertedVisibility
},
Bindings = { When, isEqualToBinding }
};
return multiBinding.ProvideValue(serviceProvider);
}
When.Converter = new HideableVisibilityConverter
{
DefaultVisibility = DefaultVisibility,
InvertedVisibility = InvertedVisibility,
IsEqualTo = IsEqualTo
};
return When.ProvideValue(serviceProvider);
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Resources.MarkupExtensions;
#nullable enable
public class HideableVisibilityConverter : IMultiValueConverter, IValueConverter {
public Visibility DefaultVisibility { get; init; } = Visibility.Visible;
public Visibility InvertedVisibility { get; init; } = Visibility.Collapsed;
public object? IsEqualTo { get; set; }
public object Convert(object?[] values, Type targetType, object? parameter, CultureInfo culture) {
if (values is not { Length: 2 })
return DependencyProperty.UnsetValue;
var value1 = values[0];
var value2 = values[1];
if (value1 is Enum enum1 && value2 is Enum enum2)
{
value1 = System.Convert.ToInt32(enum1);
value2 = System.Convert.ToInt32(enum2);
}
return Equals(value1, value2) ? InvertedVisibility : DefaultVisibility;
}
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return Equals(value, IsEqualTo) ? InvertedVisibility : DefaultVisibility;
}
public object[] ConvertBack(object? value, Type[] targetTypes, object? parameter, CultureInfo culture) {
throw new NotSupportedException();
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

View file

@ -0,0 +1,14 @@
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Resources.MarkupExtensions;
#nullable enable
public class VisibleWhenExtension : CollapsedWhenExtension
{
protected override Visibility DefaultVisibility => Visibility.Collapsed;
protected override Visibility InvertedVisibility => Visibility.Visible;
public VisibleWhenExtension(Binding when) : base(when) { }
}

View file

@ -0,0 +1,413 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:core="clr-namespace:Flow.Launcher.Core.Resource;assembly=Flow.Launcher.Core">
<converters:BorderClipConverter x:Key="BorderClipConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:TextConverter x:Key="TextConverter" />
<core:TranslationConverter x:Key="TranslationConverter" />
<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="SwitchFocusVisualStyleKey">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle
Margin="-8,-4,-8,-4"
RadiusX="5"
RadiusY="5"
Stroke="{DynamicResource Color05B}"
StrokeThickness="2" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingGrid" TargetType="ItemsControl">
<Setter Property="Focusable" Value="False" />
<Setter Property="Margin" Value="0" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="auto"
MinWidth="20"
MaxWidth="60" />
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="Auto" MinWidth="30" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingGroupBox" TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource Color00B}" />
<Setter Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Padding" Value="0,15,0,15" />
<Setter Property="SnapsToDevicePixels" Value="True" />
</Style>
<Style x:Key="SettingTitleLabel" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<Style x:Key="SettingSubTitleLabel" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="0,0,24,0" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
</Style>
<Style x:Key="TextPanel" TargetType="{x:Type StackPanel}">
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Width" Value="Auto" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style
x:Key="SideControlCheckBox"
BasedOn="{StaticResource DefaultCheckBoxStyle}"
TargetType="{x:Type CheckBox}">
<Setter Property="Width" Value="24" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Margin" Value="0,4,10,4" />
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform ScaleX="1" ScaleY="1" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SideTextAbout" TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Grid.Column" Value="1" />
<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" />
<Setter Property="HorizontalAlignment" Value="center" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="black" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Focusable" Value="false" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border>
<Grid>
<Grid>
<Border
x:Name="Spacer"
Width="Auto"
Height="Auto"
Margin="0,10,5,0"
Padding="0,0,0,0"
BorderBrush="Transparent"
BorderThickness="0">
<Border
x:Name="border"
Background="Transparent"
CornerRadius="5">
<ContentPresenter
x:Name="ContentSite"
Margin="12,12,0,12"
HorizontalAlignment="LEFT"
VerticalAlignment="Center"
ContentSource="Header"
TextBlock.Foreground="#000" />
</Border>
</Border>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="Transparent" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border" Property="Background" Value="transparent" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<!--#endregion-->
</Style>
<Style x:Key="NavTabItem" TargetType="{x:Type TabItem}">
<Setter Property="DockPanel.Dock" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
x:Name="border"
Height="40"
Margin="14,4,8,4"
Padding="0,0,0,0"
HorizontalAlignment="Stretch"
Background="{DynamicResource Color01B}"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle
x:Name="Bullet"
Grid.Column="0"
Width="4"
Height="18"
Margin="0,11,0,11"
Fill="{DynamicResource ToggleSwitchFillOn}"
RadiusX="2"
RadiusY="2"
Visibility="Hidden" />
<ContentPresenter
x:Name="ContentSite"
Grid.Column="1"
Margin="12,11,18,11"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ContentSource="Header"
TextBlock.Foreground="#000" />
</Grid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource Color06B}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource Color06B}" />
<Setter TargetName="Bullet" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PluginList" TargetType="ListBoxItem">
<Setter Property="Background" Value="{DynamicResource Color00B}" />
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Margin" Value="0,0,18,5" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="BorderBrush" Value="{DynamicResource Color03B}" />
<!--#region Template for blue highlight win10-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border
x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"
UseLayoutRounding="True">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
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 Color00B}" />
<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 Color00B}" />
<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>
<!--#endregion-->
<Setter Property="Height" Value="Auto" />
</Style>
<!--#region PluginStore Style-->
<Style x:Key="StoreList" TargetType="ListViewItem">
<Setter Property="Padding" Value="0,0,0,0" />
<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>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border
x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<!--#endregion-->
</Style>
<Style
x:Key="PluginListStyle"
BasedOn="{StaticResource {x:Type ListBox}}"
TargetType="ListBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Items.Count}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Margin="20,0,0,0">
<StackPanel>
<TextBlock
Margin="0,20,0,4"
FontWeight="Bold"
Text="{DynamicResource searchplugin_Noresult_Title}" />
<TextBlock Text="{DynamicResource searchplugin_Noresult_Subtitle}" />
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="StoreListStyle"
BasedOn="{StaticResource {x:Type ListBox}}"
TargetType="ListBox">
<Setter Property="Background" Value="{DynamicResource Color01B}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Items.Count}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Margin="20,0,0,0">
<StackPanel>
<TextBlock
Margin="0,20,0,4"
FontWeight="Bold"
Text="{DynamicResource searchplugin_Noresult_Title}" />
<TextBlock Text="{DynamicResource searchplugin_Noresult}" />
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- For Tab Header responsive Width -->
<Style x:Key="NavTabControl" TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid
x:Name="templateRoot"
ClipToBounds="true"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition
x:Name="ColumnDefinition0"
Width="Auto"
MinWidth="230" />
<ColumnDefinition x:Name="ColumnDefinition1" Width="7.5*" />
</Grid.ColumnDefinitions>
<!-- here is the edit -->
<DockPanel
x:Name="headerPanel"
Grid.Row="0"
Grid.Column="0"
Margin="2,2,2,0"
Panel.ZIndex="1"
Background="Transparent"
IsItemsHost="true"
LastChildFill="False" />
<Border Grid.Column="1">
<ContentPresenter
x:Name="PART_SelectedContentHost"
Grid.Column="1"
ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.SettingPages.ViewModels;
public class DropdownDataGeneric<TValue> : BaseModel where TValue : Enum
{
public string Display { get; set; }
public TValue Value { get; set; }
public static List<TR> GetValues<TR>(string keyPrefix) where TR : DropdownDataGeneric<TValue>, new()
{
var data = new List<TR>();
var enumValues = (TValue[])Enum.GetValues(typeof(TValue));
foreach (var value in enumValues)
{
var key = keyPrefix + value;
var display = InternationalizationManager.Instance.GetTranslation(key);
data.Add(new TR { Display = display, Value = value });
}
return data;
}
}

View file

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneAboutViewModel : BaseModel
{
private readonly Settings _settings;
private readonly Updater _updater;
public string LogFolderSize
{
get
{
var size = GetLogFiles().Sum(file => file.Length);
return $"{InternationalizationManager.Instance.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})";
}
}
public string Website => Constant.Website;
public string SponsorPage => Constant.SponsorPage;
public string ReleaseNotes => _updater.GitHubRepository + "/releases/latest";
public string Documentation => Constant.Documentation;
public string Docs => Constant.Docs;
public string Github => Constant.GitHub;
public string Version => Constant.Version switch
{
"1.0.0" => Constant.Dev,
_ => Constant.Version
};
public string ActivatedTimes => string.Format(
InternationalizationManager.Instance.GetTranslation("about_activate_times"),
_settings.ActivateTimes
);
public SettingsPaneAboutViewModel(Settings settings, Updater updater)
{
_settings = settings;
_updater = updater;
}
[RelayCommand]
private void OpenWelcomeWindow()
{
var window = new WelcomeWindow(_settings);
window.ShowDialog();
}
[RelayCommand]
private void AskClearLogFolderConfirmation()
{
var confirmResult = MessageBox.Show(
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
MessageBoxButton.YesNo
);
if (confirmResult == MessageBoxResult.Yes)
{
ClearLogFolder();
}
}
[RelayCommand]
private void OpenSettingsFolder()
{
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
}
[RelayCommand]
private void OpenLogsFolder()
{
App.API.OpenDirectory(GetLogDir(Constant.Version).FullName);
}
[RelayCommand]
private Task UpdateApp() => _updater.UpdateAppAsync(App.API, false);
private void ClearLogFolder()
{
var logDirectory = GetLogDir();
var logFiles = GetLogFiles();
logFiles.ForEach(f => f.Delete());
logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.Where(dir => !Constant.Version.Equals(dir.Name))
.ToList()
.ForEach(dir => dir.Delete());
OnPropertyChanged(nameof(LogFolderSize));
}
private static DirectoryInfo GetLogDir(string version = "")
{
return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version));
}
private static List<FileInfo> GetLogFiles(string version = "")
{
return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
}
private static string BytesToReadableString(long bytes)
{
const int scale = 1024;
string[] orders = { "GB", "MB", "KB", "B" };
long max = (long)Math.Pow(scale, orders.Length - 1);
foreach (string order in orders)
{
if (bytes > max)
return $"{decimal.Divide(bytes, max):##.##} {order}";
max /= scale;
}
return "0 B";
}
}

View file

@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneGeneralViewModel : BaseModel
{
public Settings Settings { get; }
private readonly Updater _updater;
private readonly IPortable _portable;
public SettingsPaneGeneralViewModel(Settings settings, Updater updater, IPortable portable)
{
Settings = settings;
_updater = updater;
_portable = portable;
UpdateLastQueryModeDisplay();
}
public class SearchWindowScreen : DropdownDataGeneric<SearchWindowScreens> { }
public class SearchWindowAlign : DropdownDataGeneric<SearchWindowAligns> { }
// todo a better name?
public class LastQueryMode : DropdownDataGeneric<Infrastructure.UserSettings.LastQueryMode> { }
public bool StartFlowLauncherOnSystemStartup
{
get => Settings.StartFlowLauncherOnSystemStartup;
set
{
Settings.StartFlowLauncherOnSystemStartup = value;
try
{
if (value)
AutoStartup.Enable();
else
AutoStartup.Disable();
}
catch (Exception e)
{
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
e.Message);
}
}
}
public List<SearchWindowScreen> SearchWindowScreens =>
DropdownDataGeneric<SearchWindowScreens>.GetValues<SearchWindowScreen>("SearchWindowScreen");
public List<SearchWindowAlign> SearchWindowAligns =>
DropdownDataGeneric<SearchWindowAligns>.GetValues<SearchWindowAlign>("SearchWindowAlign");
public List<int> ScreenNumbers
{
get
{
var screens = Screen.AllScreens;
var screenNumbers = new List<int>();
for (int i = 1; i <= screens.Length; i++)
{
screenNumbers.Add(i);
}
return screenNumbers;
}
}
// This is only required to set at startup. When portable mode enabled/disabled a restart is always required
private bool _portableMode = DataLocation.PortableDataLocationInUse();
public bool PortableMode
{
get => _portableMode;
set
{
if (!_portable.CanUpdatePortability())
return;
if (DataLocation.PortableDataLocationInUse())
{
_portable.DisablePortableMode();
}
else
{
_portable.EnablePortableMode();
}
}
}
private List<LastQueryMode> _lastQueryModes = new();
public List<LastQueryMode> LastQueryModes
{
get
{
if (_lastQueryModes.Count == 0)
{
_lastQueryModes =
DropdownDataGeneric<Infrastructure.UserSettings.LastQueryMode>
.GetValues<LastQueryMode>("LastQuery");
}
return _lastQueryModes;
}
}
private void UpdateLastQueryModeDisplay()
{
foreach (var item in LastQueryModes)
{
item.Display = InternationalizationManager.Instance.GetTranslation($"LastQuery{item.Value}");
}
}
public string Language
{
get => Settings.Language;
set
{
InternationalizationManager.Instance.ChangeLanguage(value);
if (InternationalizationManager.Instance.PromptShouldUsePinyin(value))
ShouldUsePinyin = true;
UpdateLastQueryModeDisplay();
}
}
public bool ShouldUsePinyin
{
get => Settings.ShouldUsePinyin;
set => Settings.ShouldUsePinyin = value;
}
public List<string> QuerySearchPrecisionStrings => Enum
.GetValues(typeof(SearchPrecisionScore))
.Cast<SearchPrecisionScore>()
.Select(v => v.ToString())
.ToList();
public List<Language> Languages => InternationalizationManager.Instance.LoadAvailableLanguages();
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
public string AlwaysPreviewToolTip => string.Format(
InternationalizationManager.Instance.GetTranslation("AlwaysPreviewToolTip"),
Settings.PreviewHotkey
);
private string GetFileFromDialog(string title, string filter = "")
{
var dlg = new OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Multiselect = false,
CheckFileExists = true,
CheckPathExists = true,
Title = title,
Filter = filter
};
return dlg.ShowDialog() switch
{
DialogResult.OK => dlg.FileName,
_ => string.Empty
};
}
private void UpdateApp()
{
_ = _updater.UpdateAppAsync(App.API, false);
}
public bool AutoUpdates
{
get => Settings.AutoUpdates;
set
{
Settings.AutoUpdates = value;
if (value)
{
UpdateApp();
}
}
}
[RelayCommand]
private void SelectPython()
{
var selectedFile = GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"),
"Python|pythonw.exe"
);
if (!string.IsNullOrEmpty(selectedFile))
Settings.PluginSettings.PythonExecutablePath = selectedFile;
}
[RelayCommand]
private void SelectNode()
{
var selectedFile = GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectNodeExecutable"),
"*.exe"
);
if (!string.IsNullOrEmpty(selectedFile))
Settings.PluginSettings.NodeExecutablePath = selectedFile;
}
[RelayCommand]
private void SelectFileManager()
{
var fileManagerChangeWindow = new SelectFileManagerWindow(Settings);
fileManagerChangeWindow.ShowDialog();
}
[RelayCommand]
private void SelectBrowser()
{
var browserWindow = new SelectBrowserWindow(Settings);
browserWindow.ShowDialog();
}
}

View file

@ -0,0 +1,134 @@
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneHotkeyViewModel : BaseModel
{
public Settings Settings { get; }
public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; }
public CustomShortcutModel SelectedCustomShortcut { get; set; }
public string[] OpenResultModifiersList => new[]
{
KeyConstant.Alt,
KeyConstant.Ctrl,
$"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
};
public SettingsPaneHotkeyViewModel(Settings settings)
{
Settings = settings;
}
[RelayCommand]
private void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
}
[RelayCommand]
private void CustomHotkeyDelete()
{
var item = SelectedCustomPluginHotkey;
if (item is null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
var result = MessageBox.Show(
string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
),
InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo
);
if (result is MessageBoxResult.Yes)
{
Settings.CustomPluginHotkeys.Remove(item);
HotKeyMapper.RemoveHotkey(item.Hotkey);
}
}
[RelayCommand]
private void CustomHotkeyEdit()
{
var item = SelectedCustomPluginHotkey;
if (item is null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
var window = new CustomQueryHotkeySetting(null, Settings);
window.UpdateItem(item);
window.ShowDialog();
}
[RelayCommand]
private void CustomHotkeyAdd()
{
new CustomQueryHotkeySetting(null, Settings).ShowDialog();
}
[RelayCommand]
private void CustomShortcutDelete()
{
var item = SelectedCustomShortcut;
if (item is null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
var result = MessageBox.Show(
string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
),
InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo
);
if (result is MessageBoxResult.Yes)
{
Settings.CustomShortcuts.Remove(item);
}
}
[RelayCommand]
private void CustomShortcutEdit()
{
var item = SelectedCustomShortcut;
if (item is null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
var window = new CustomShortcutSetting(item.Key, item.Value);
if (window.ShowDialog() is not true) return;
var index = Settings.CustomShortcuts.IndexOf(item);
Settings.CustomShortcuts[index] = new CustomShortcutModel(window.Key, window.Value);
}
[RelayCommand]
private void CustomShortcutAdd()
{
var window = new CustomShortcutSetting(null);
if (window.ShowDialog() is true)
{
var shortcut = new CustomShortcutModel(window.Key, window.Value);
Settings.CustomShortcuts.Add(shortcut);
}
}
}

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPanePluginStoreViewModel : BaseModel
{
public string FilterText { get; set; } = string.Empty;
public IList<PluginStoreItemViewModel> ExternalPlugins => PluginsManifest.UserPlugins
.Select(p => new PluginStoreItemViewModel(p))
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed)
.ToList();
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
await PluginsManifest.UpdateManifestAsync();
OnPropertyChanged(nameof(ExternalPlugins));
}
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
{
return string.IsNullOrEmpty(FilterText) ||
StringMatcher.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
StringMatcher.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
}
}

View file

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
#nullable enable
namespace Flow.Launcher.SettingPages.ViewModels;
public class SettingsPanePluginsViewModel : BaseModel
{
private readonly Settings _settings;
public SettingsPanePluginsViewModel(Settings settings)
{
_settings = settings;
}
public string FilterText { get; set; } = string.Empty;
public PluginViewModel? SelectedPlugin { get; set; }
private IEnumerable<PluginViewModel>? _pluginViewModels;
private IEnumerable<PluginViewModel> PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins
.OrderBy(plugin => plugin.Metadata.Disabled)
.ThenBy(plugin => plugin.Metadata.Name)
.Select(plugin => new PluginViewModel
{
PluginPair = plugin,
PluginSettingsObject = _settings.PluginSettings.Plugins[plugin.Metadata.ID]
})
.ToList();
public List<PluginViewModel> FilteredPluginViewModels => PluginViewModels
.Where(v =>
string.IsNullOrEmpty(FilterText) ||
StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
)
.ToList();
}

View file

@ -0,0 +1,62 @@
using System.Net;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneProxyViewModel : BaseModel
{
private readonly Updater _updater;
public Settings Settings { get; }
public SettingsPaneProxyViewModel(Settings settings, Updater updater)
{
_updater = updater;
Settings = settings;
}
[RelayCommand]
private void OnTestProxyClicked()
{
var message = TestProxy();
MessageBox.Show(InternationalizationManager.Instance.GetTranslation(message));
}
private string TestProxy()
{
if (string.IsNullOrEmpty(Settings.Proxy.Server)) return "serverCantBeEmpty";
if (Settings.Proxy.Port <= 0) return "portCantBeEmpty";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
if (string.IsNullOrEmpty(Settings.Proxy.UserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
{
request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port);
}
else
{
request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port)
{
Credentials = new NetworkCredential(Settings.Proxy.UserName, Settings.Proxy.Password)
};
}
try
{
var response = (HttpWebResponse)request.GetResponse();
return response.StatusCode switch
{
HttpStatusCode.OK => "proxyIsCorrect",
_ => "proxyConnectFailed"
};
}
catch
{
return "proxyConnectFailed";
}
}
}

View file

@ -0,0 +1,392 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using ModernWpf;
using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneThemeViewModel : BaseModel
{
private CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture;
public Settings Settings { get; }
public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
public string SelectedTheme
{
get => Settings.Theme;
set
{
ThemeManager.Instance.ChangeTheme(value);
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
DropShadowEffect = false;
}
}
public bool DropShadowEffect
{
get => Settings.UseDropShadowEffect;
set
{
if (ThemeManager.Instance.BlurEnabled && value)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
return;
}
if (value)
{
ThemeManager.Instance.AddDropShadowEffectToCurrentTheme();
}
else
{
ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme();
}
Settings.UseDropShadowEffect = value;
}
}
public List<string> Themes =>
ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList();
public class ColorScheme
{
public string Display { get; set; }
public ColorSchemes Value { get; set; }
}
public List<ColorScheme> ColorSchemes
{
get
{
List<ColorScheme> modes = new List<ColorScheme>();
var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes));
foreach (var e in enums)
{
var key = $"ColorScheme{e}";
var display = InternationalizationManager.Instance.GetTranslation(key);
var m = new ColorScheme { Display = display, Value = e, };
modes.Add(m);
}
return modes;
}
}
public List<string> TimeFormatList { get; } = new()
{
"h:mm",
"hh:mm",
"H:mm",
"HH:mm",
"tt h:mm",
"tt hh:mm",
"h:mm tt",
"hh:mm tt",
"hh:mm:ss tt",
"HH:mm:ss"
};
public List<string> DateFormatList { get; } = new()
{
"MM'/'dd dddd",
"MM'/'dd ddd",
"MM'/'dd",
"MM'-'dd",
"MMMM', 'dd",
"dd'/'MM",
"dd'-'MM",
"ddd MM'/'dd",
"dddd MM'/'dd",
"dddd",
"ddd dd'/'MM",
"dddd dd'/'MM",
"dddd dd', 'MMMM",
"dd', 'MMMM"
};
public string TimeFormat
{
get => Settings.TimeFormat;
set => Settings.TimeFormat = value;
}
public string DateFormat
{
get => Settings.DateFormat;
set => Settings.DateFormat = value;
}
public string ClockText => DateTime.Now.ToString(TimeFormat, Culture);
public string DateText => DateTime.Now.ToString(DateFormat, Culture);
public double WindowWidthSize
{
get => Settings.WindowSize;
set => Settings.WindowSize = value;
}
public bool UseGlyphIcons
{
get => Settings.UseGlyphIcons;
set => Settings.UseGlyphIcons = value;
}
public bool UseAnimation
{
get => Settings.UseAnimation;
set => Settings.UseAnimation = value;
}
public class AnimationSpeed
{
public string Display { get; set; }
public AnimationSpeeds Value { get; set; }
}
public List<AnimationSpeed> AnimationSpeeds
{
get
{
List<AnimationSpeed> speeds = new List<AnimationSpeed>();
var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds));
foreach (var e in enums)
{
var key = $"AnimationSpeed{e}";
var display = InternationalizationManager.Instance.GetTranslation(key);
var m = new AnimationSpeed { Display = display, Value = e, };
speeds.Add(m);
}
return speeds;
}
}
public bool UseSound
{
get => Settings.UseSound;
set => Settings.UseSound = value;
}
public double SoundEffectVolume
{
get => Settings.SoundVolume;
set => Settings.SoundVolume = value;
}
public bool UseClock
{
get => Settings.UseClock;
set => Settings.UseClock = value;
}
public bool UseDate
{
get => Settings.UseDate;
set => Settings.UseDate = value;
}
public Brush PreviewBackground
{
get
{
var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
if (wallpaper is not null && File.Exists(wallpaper))
{
var memStream = new MemoryStream(File.ReadAllBytes(wallpaper));
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = memStream;
bitmap.DecodePixelWidth = 800;
bitmap.DecodePixelHeight = 600;
bitmap.EndInit();
return new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
}
var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
}
}
public ResultsViewModel PreviewResults
{
get
{
var results = new List<Result>
{
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
)
}
};
var vm = new ResultsViewModel(Settings);
vm.AddResults(results, "PREVIEW");
return vm;
}
}
public FontFamily SelectedQueryBoxFont
{
get
{
var fontExists = Fonts.SystemFontFamilies.Any(
fontFamily =>
fontFamily.FamilyNames.Values != null &&
fontFamily.FamilyNames.Values.Contains(Settings.QueryBoxFont)
);
return fontExists switch
{
true => new FontFamily(Settings.QueryBoxFont),
_ => new FontFamily("Segoe UI")
};
}
set
{
Settings.QueryBoxFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FamilyTypeface SelectedQueryBoxFontFaces
{
get
{
var typeface = SyntaxSugars.CallOrRescueDefault(
() => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal(
Settings.QueryBoxFontStyle,
Settings.QueryBoxFontWeight,
Settings.QueryBoxFontStretch
)
);
return typeface;
}
set
{
Settings.QueryBoxFontStretch = value.Stretch.ToString();
Settings.QueryBoxFontWeight = value.Weight.ToString();
Settings.QueryBoxFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FontFamily SelectedResultFont
{
get
{
var fontExists = Fonts.SystemFontFamilies.Any(
fontFamily =>
fontFamily.FamilyNames.Values != null &&
fontFamily.FamilyNames.Values.Contains(Settings.ResultFont)
);
return fontExists switch
{
true => new FontFamily(Settings.ResultFont),
_ => new FontFamily("Segoe UI")
};
}
set
{
Settings.ResultFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FamilyTypeface SelectedResultFontFaces
{
get
{
var typeface = SyntaxSugars.CallOrRescueDefault(
() => SelectedResultFont.ConvertFromInvariantStringsOrNormal(
Settings.ResultFontStyle,
Settings.ResultFontWeight,
Settings.ResultFontStretch
)
);
return typeface;
}
set
{
Settings.ResultFontStretch = value.Stretch.ToString();
Settings.ResultFontWeight = value.Weight.ToString();
Settings.ResultFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public string ThemeImage => Constant.QueryTextBoxIconImagePath;
[RelayCommand]
private void OpenThemesFolder()
{
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
}
public void UpdateColorScheme()
{
ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = Settings.ColorScheme switch
{
Constant.Light => ApplicationTheme.Light,
Constant.Dark => ApplicationTheme.Dark,
Constant.System => null,
_ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme
};
}
public SettingsPaneThemeViewModel(Settings settings)
{
Settings = settings;
}
}

View file

@ -0,0 +1,127 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneAbout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:settingsVm="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
xmlns:ui="http://schemas.modernwpf.com/2019"
Title="About"
d:DataContext="{d:DesignInstance Type=settingsVm:SettingsPaneAboutViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<ScrollViewer
Margin="0"
CanContentScroll="True"
FontSize="14"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel Margin="5 14 25 30" Orientation="Vertical">
<TextBlock
Margin="0 5"
FontSize="30"
Style="{DynamicResource PageTitle}"
Text="{DynamicResource about}"
TextAlignment="left" />
<cc:Card
Title="{Binding Version}"
Icon="&#xe946;"
Sub="{DynamicResource version}">
<StackPanel Orientation="Horizontal">
<Button
Margin="0 0 10 0"
Command="{Binding UpdateAppCommand}"
Content="{DynamicResource checkUpdates}" />
<Button Padding="0" Style="{StaticResource AccentButtonStyle}">
<Hyperlink
NavigateUri="{Binding SponsorPage}"
RequestNavigate="OnRequestNavigate"
TextDecorations="None">
<TextBlock
Padding="10 5"
Foreground="White"
Text="{DynamicResource BecomeASponsor}" />
</Hyperlink>
</Button>
</StackPanel>
</cc:Card>
<cc:Card Title="{DynamicResource releaseNotes}" Icon="&#xe8fd;">
<cc:HyperLink Uri="{Binding ReleaseNotes}" Text="{DynamicResource releaseNotes}" />
</cc:Card>
<cc:Card
Title="{DynamicResource website}"
Margin="0 14 0 0"
Icon="&#xeb41;">
<StackPanel Orientation="Horizontal">
<cc:HyperLink Margin="0 0 12 0" Uri="{Binding Website}" Text="{DynamicResource website}" />
<cc:HyperLink Uri="{Binding Github}" Text="{DynamicResource github}" />
</StackPanel>
</cc:Card>
<cc:Card Title="{DynamicResource documentation}" Icon="&#xe82f;">
<StackPanel Orientation="Horizontal">
<cc:HyperLink Margin="0 0 12 0" Uri="{Binding Documentation}" Text="{DynamicResource documentation}" />
<cc:HyperLink Uri="{Binding Website}" Text="{DynamicResource website}" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource icons}"
Margin="0 14 0 0"
Icon="&#xE8FE;">
<cc:HyperLink Uri="https://icons8.com/" Text="icons8.com" />
</cc:Card>
<cc:Card Title="{DynamicResource devtool}" Icon="&#xf12b;">
<StackPanel Orientation="Horizontal">
<Button
Margin="0 0 12 0"
Command="{Binding AskClearLogFolderConfirmationCommand}"
Content="{Binding LogFolderSize, Mode=OneWay}" />
<Button
Content="&#xec7a;"
FontFamily="/Resources/#Segoe Fluent Icons"
FontSize="20">
<ui:FlyoutService.Flyout>
<ui:MenuFlyout>
<MenuItem Command="{Binding OpenWelcomeWindowCommand}" Header="{DynamicResource welcomewindow}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe939;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding OpenSettingsFolderCommand}" Header="{DynamicResource settingfolder}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe8b7;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding OpenLogsFolderCommand}" Header="{DynamicResource logfolder}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xe8b7;" />
</MenuItem.Icon>
</MenuItem>
</ui:MenuFlyout>
</ui:FlyoutService.Flyout>
</Button>
</StackPanel>
</cc:Card>
<TextBlock
Margin="14 20 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
DockPanel.Dock="Bottom"
FontSize="12"
Foreground="{DynamicResource Color15B}"
Text="{Binding ActivatedTimes}"
TextWrapping="WrapWithOverflow" />
</StackPanel>
</ScrollViewer>
</ui:Page>

View file

@ -0,0 +1,29 @@
using System;
using System.Windows.Navigation;
using Flow.Launcher.SettingPages.ViewModels;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneAbout
{
private SettingsPaneAboutViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater })
throw new ArgumentException("Settings are required for SettingsPaneAbout.");
_viewModel = new SettingsPaneAboutViewModel(settings, updater);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
App.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
}

View file

@ -0,0 +1,244 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneGeneral"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:settingsViewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:ext="clr-namespace:Flow.Launcher.Resources.MarkupExtensions"
Title="General"
d:DataContext="{d:DesignInstance settingsViewModels:SettingsPaneGeneralViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<ScrollViewer
Margin="0"
CanContentScroll="False"
FontSize="14"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.IsVirtualizing="True">
<VirtualizingStackPanel Margin="5 18 25 30" Orientation="Vertical">
<TextBlock
Grid.Row="2"
Margin="0 5"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource general}"
TextAlignment="left" />
<cc:Card Title="{DynamicResource startFlowLauncherOnSystemStartup}" Icon="&#xe8fc;">
<ui:ToggleSwitch IsOn="{Binding StartFlowLauncherOnSystemStartup}" />
</cc:Card>
<cc:Card Title="{DynamicResource hideOnStartup}" Icon="&#xed1a;">
<ui:ToggleSwitch IsOn="{Binding Settings.HideOnStartup}" />
</cc:Card>
<cc:Card Title="{DynamicResource hideFlowLauncherWhenLoseFocus}" Margin="0 30 0 0">
<ui:ToggleSwitch IsOn="{Binding Settings.HideWhenDeactivated}" />
</cc:Card>
<cc:Card Title="{DynamicResource hideNotifyIcon}" Sub="{DynamicResource hideNotifyIconToolTip}">
<ui:ToggleSwitch IsOn="{Binding Settings.HideNotifyIcon}" />
</cc:Card>
<cc:CardGroup Margin="0 30 0 0">
<cc:Card Icon="&#xe7f4;" Title="{DynamicResource SearchWindowPosition}">
<StackPanel Orientation="Horizontal">
<ComboBox
MinWidth="220"
DisplayMemberPath="Display"
SelectedValuePath="Value"
FontSize="14"
VerticalAlignment="Center"
ItemsSource="{Binding SearchWindowScreens}"
SelectedValue="{Binding Settings.SearchWindowScreen}" />
<ComboBox
MinWidth="160"
Margin="18 0 0 0"
FontSize="14"
VerticalAlignment="Center"
ItemsSource="{Binding ScreenNumbers}"
SelectedValue="{Binding Settings.CustomScreenNumber}"
Visibility="{ext:VisibleWhen
{Binding Settings.SearchWindowScreen},
IsEqualTo={x:Static userSettings:SearchWindowScreens.Custom}}"
/>
</StackPanel>
</cc:Card>
<cc:Card
Icon="&#xe7f4;"
Title="{DynamicResource SearchWindowAlign}"
Visibility="{ext:CollapsedWhen
{Binding Settings.SearchWindowScreen},
IsEqualTo={x:Static userSettings:SearchWindowScreens.RememberLastLaunchLocation}}">
<StackPanel Orientation="Horizontal">
<ComboBox
MinWidth="160"
DisplayMemberPath="Display"
SelectedValuePath="Value"
FontSize="14"
VerticalAlignment="Center"
ItemsSource="{Binding SearchWindowAligns}"
SelectedValue="{Binding Settings.SearchWindowAlign}" />
<StackPanel
Margin="18 0 0 0"
Orientation="Horizontal"
VerticalAlignment="Center"
Visibility="{ext:VisibleWhen
{Binding Settings.SearchWindowAlign},
IsEqualTo={x:Static userSettings:SearchWindowAligns.Custom}}">
<TextBox VerticalAlignment="Center" MinWidth="80"
Text="{Binding Settings.CustomWindowLeft}" />
<TextBlock VerticalAlignment="Center" Margin="10" Text="x" />
<TextBox VerticalAlignment="Center" MinWidth="80" Text="{Binding Settings.CustomWindowTop}"
TextWrapping="NoWrap" />
</StackPanel>
</StackPanel>
</cc:Card>
</cc:CardGroup>
<cc:Card
Title="{DynamicResource ignoreHotkeysOnFullscreen}"
Icon="&#xe7fc;"
Sub="{DynamicResource ignoreHotkeysOnFullscreenToolTip}">
<ui:ToggleSwitch IsOn="{Binding Settings.IgnoreHotkeysOnFullscreen}" />
</cc:Card>
<cc:Card
Title="{DynamicResource AlwaysPreview}"
Margin="0 30 0 0"
Icon="&#xe8a1;"
Sub="{DynamicResource AlwaysPreviewToolTip}">
<ui:ToggleSwitch IsOn="{Binding Settings.AlwaysPreview}" ToolTip="{Binding AlwaysPreviewToolTip}" />
</cc:Card>
<cc:Card
Title="{DynamicResource autoUpdates}"
Margin="0 30 0 0"
Icon="&#xecc5;">
<ui:ToggleSwitch IsOn="{Binding AutoUpdates}" />
</cc:Card>
<cc:Card
Title="{DynamicResource portableMode}"
Icon="&#xe88e;"
Sub="{DynamicResource portableModeToolTIp}">
<ui:ToggleSwitch IsOn="{Binding PortableMode}" />
</cc:Card>
<cc:CardGroup Margin="0 30 0 0">
<cc:Card Title="{DynamicResource querySearchPrecision}"
Sub="{DynamicResource querySearchPrecisionToolTip}">
<ComboBox
MaxWidth="200"
ItemsSource="{Binding QuerySearchPrecisionStrings}"
SelectedItem="{Binding Settings.QuerySearchPrecisionString}" />
</cc:Card>
<cc:Card Title="{DynamicResource lastQueryMode}" Sub="{DynamicResource lastQueryModeToolTip}">
<ComboBox
DisplayMemberPath="Display"
SelectedValuePath="Value"
ItemsSource="{Binding LastQueryModes}"
SelectedValue="{Binding Settings.LastQueryMode}" />
</cc:Card>
<cc:Card Icon="&#xe8fd;" Title="{DynamicResource maxShowResults}"
Sub="{DynamicResource maxShowResultsToolTip}">
<ComboBox
Width="100"
ItemsSource="{Binding MaxResultsRange}"
SelectedItem="{Binding Settings.MaxResultsToShow}" />
</cc:Card>
</cc:CardGroup>
<cc:Card
Title="{DynamicResource defaultFileManager}"
Margin="0 30 0 0"
Icon="&#xe838;"
Sub="{DynamicResource defaultFileManagerToolTip}">
<Button
Width="160"
MaxWidth="250"
Margin="10 0 0 0"
Command="{Binding SelectFileManagerCommand}"
Content="{Binding Settings.CustomExplorer.Name}" />
</cc:Card>
<cc:Card
Title="{DynamicResource defaultBrowser}"
Icon="&#xf6fa;"
Sub="{DynamicResource defaultBrowserToolTip}">
<Button
Width="160"
MaxWidth="250"
Margin="10 0 0 0"
Command="{Binding SelectBrowserCommand}"
Content="{Binding Settings.CustomBrowser.Name}" />
</cc:Card>
<cc:Card Title="{DynamicResource pythonFilePath}" Margin="0 30 0 0">
<StackPanel Orientation="Horizontal">
<TextBox
Width="300"
Height="34"
Text="{Binding Settings.PluginSettings.PythonExecutablePath, TargetNullValue='None'}" />
<Button
Height="34"
Margin="10 0 0 0"
Command="{Binding SelectPythonCommand}"
Content="{DynamicResource select}" />
</StackPanel>
</cc:Card>
<cc:Card Title="{DynamicResource nodeFilePath}">
<StackPanel Orientation="Horizontal">
<TextBox
Width="300"
Height="34"
Text="{Binding Settings.PluginSettings.NodeExecutablePath, TargetNullValue='None'}" />
<Button
Height="34"
Margin="10 0 0 0"
Command="{Binding SelectNodeCommand}"
Content="{DynamicResource select}" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource typingStartEn}"
Margin="0 30 0 0"
Icon="&#xe8d3;"
Sub="{DynamicResource typingStartEnTooltip}">
<ui:ToggleSwitch IsOn="{Binding Settings.AlwaysStartEn}" />
</cc:Card>
<cc:Card
Title="{DynamicResource ShouldUsePinyin}"
Icon="&#xe98a;"
Sub="{DynamicResource ShouldUsePinyinToolTip}">
<ui:ToggleSwitch IsOn="{Binding Settings.ShouldUsePinyin}"
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
</cc:Card>
<cc:Card
Title="{DynamicResource language}"
Margin="0 30 0 0"
Icon="&#xf2b7;">
<ComboBox
MaxWidth="200"
Margin="10 0 0 0"
DisplayMemberPath="Display"
ItemsSource="{Binding Languages}"
SelectedValue="{Binding Language}"
SelectedValuePath="LanguageCode" />
</cc:Card>
</VirtualizingStackPanel>
</ScrollViewer>
</ui:Page>

View file

@ -0,0 +1,24 @@
using System;
using System.Windows.Navigation;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.SettingPages.ViewModels;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneGeneral
{
private SettingsPaneGeneralViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: {} updater, Portable: {} portable })
throw new ArgumentException("Settings, Updater and Portable are required for SettingsPaneGeneral.");
_viewModel = new SettingsPaneGeneralViewModel(settings, updater, portable);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
}

View file

@ -0,0 +1,452 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneHotkey"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="Hotkey"
d:DataContext="{d:DesignInstance viewModels:SettingsPaneHotkeyViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<ScrollViewer
Padding="0 0 6 0"
FontSize="14"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel Margin="5 18 18 10">
<TextBlock
Margin="0 5 0 6"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource hotkeys}"
TextAlignment="left" />
<cc:Card
Title="{DynamicResource flowlauncherHotkey}"
Icon="&#xeda7;"
Sub="{DynamicResource flowlauncherHotkeyToolTip}">
<flowlauncher:HotkeyControl
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
HotkeySettings="{Binding Settings}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
</cc:Card>
<cc:Card
Title="{DynamicResource previewHotkey}"
Icon="&#xe8a1;"
Sub="{DynamicResource previewHotkeyToolTip}">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="F1"
Hotkey="{Binding Settings.PreviewHotkey}"
ValidateKeyGesture="False"
WindowTitle="{DynamicResource previewHotkey}" />
</cc:Card>
<cc:CardGroup Margin="0 12 0 0">
<cc:Card
Title="{DynamicResource openResultModifiers}"
Sub="{DynamicResource openResultModifiersToolTip}">
<ComboBox
Width="120"
FontSize="14"
ItemsSource="{Binding OpenResultModifiersList}"
SelectedValue="{Binding Settings.OpenResultModifiers}" />
</cc:Card>
<cc:Card
Title="{DynamicResource showOpenResultHotkey}"
Sub="{DynamicResource showOpenResultHotkeyToolTip}">
<ui:ToggleSwitch IsOn="{Binding Settings.ShowOpenResultHotkey}" />
</cc:Card>
</cc:CardGroup>
<cc:ExCard
Title="{DynamicResource hotkeyPresets}"
Margin="0 14 0 0"
Icon="&#xf0e2;"
Sub="{DynamicResource hotkeyPresetsToolTip}">
<StackPanel>
<cc:Card
Title="{DynamicResource OpenContainFolderHotkey}"
Icon="&#xe8b7;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource RunAsAdminHotkey}"
Icon="&#xe7ef;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Shift+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource ToggleHistoryHotkey}"
Icon="&#xf738;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+H" />
</cc:Card>
<cc:Card
Title="{DynamicResource CopyFilePathHotkey}"
Icon="&#xe8c8;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Shift+C" />
</cc:Card>
<cc:Card
Title="{DynamicResource OpenContextMenuHotkey}"
Icon="&#xede3;"
Type="Inside">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.OpenContextMenuHotkey}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource OpenContextMenuHotkey}"
Icon="&#xede3;"
Type="Inside">
<cc:HotkeyDisplay Keys="Shift+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource SettingWindowHotkey}"
Icon="&#xe713;"
Type="Inside">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.SettingWindowHotkey}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource ToggleGameModeHotkey}"
Icon="&#xe7fc;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+F12" />
</cc:Card>
<cc:Card
Title="{DynamicResource RequeryHotkey}"
Icon="&#xe72c;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+R" />
</cc:Card>
<cc:Card
Title="{DynamicResource CycleHistoryUpHotkey}"
Icon="&#xe70e;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey="Alt+Up"
Hotkey="{Binding Settings.CycleHistoryUpHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource CycleHistoryDownHotkey}"
Icon="&#xe70d;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey="Alt+Down"
Hotkey="{Binding Settings.CycleHistoryDownHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource ReloadPluginHotkey}"
Icon="&#xe72c;"
Sub="{DynamicResource ReloadPluginHotkeyToolTip}"
Type="Inside">
<cc:HotkeyDisplay Keys="F5" />
</cc:Card>
<cc:Card
Title="{DynamicResource SelectPrevPageHotkey}"
Icon="&#xf0ad;"
Type="Inside">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevPageHotkey}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource SelectNextPageHotkey}"
Icon="&#xf0ae;"
Type="Inside">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextPageHotkey}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource QuickWidthHotkey}"
Icon="&#xe7ea;"
Type="Inside">
<StackPanel Orientation="Horizontal">
<cc:HotkeyDisplay Keys="Ctrl+[" />
<cc:HotkeyDisplay Margin="4 0 0 0" Keys="Ctrl+]" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource QuickHeightHotkey}"
Icon="&#xe7eb;"
Type="Inside">
<StackPanel Orientation="Horizontal">
<cc:HotkeyDisplay Keys="Ctrl+Plus" />
<cc:HotkeyDisplay Margin="4 0 0 0" Keys="Ctrl+Minus" />
</StackPanel>
</cc:Card>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource autoCompleteHotkey}"
Margin="0 14 0 0"
Icon="&#xe893;"
Sub="{DynamicResource autoCompleteHotkeyToolTip}">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="Ctrl+Tab"
Hotkey="{Binding Settings.AutoCompleteHotkey}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource autoCompleteHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey=""
Hotkey="{Binding Settings.AutoCompleteHotkey2}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource SelectPrevItemHotkey}"
Margin="0 4 0 0"
Icon="&#xe74a;">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="Shift+Tab"
Hotkey="{Binding Settings.SelectPrevItemHotkey}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource SelectPrevItemHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevItemHotkey2}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource SelectNextItemHotkey}"
Margin="0 4 0 0"
Icon="&#xe74b;">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey="Tab"
Hotkey="{Binding Settings.SelectNextItemHotkey}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource SelectNextItemHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
HotkeySettings="{Binding Settings}"
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextItemHotkey2}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource customQueryHotkey}"
Margin="0 20 0 0"
Icon="&#xf26c;">
<StackPanel>
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="18 18 18 0">
<ListView
MinHeight="160"
Margin="0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding Settings.CustomPluginHotkeys}"
SelectedItem="{Binding SelectedCustomPluginHotkey}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource hotkey}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="userSettings:CustomPluginHotkey">
<TextBlock Text="{Binding Hotkey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource customQuery}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="userSettings:CustomPluginHotkey">
<TextBlock Text="{Binding ActionKeyword}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Command="{Binding CustomHotkeyDeleteCommand}"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Command="{Binding CustomHotkeyEditCommand}"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10 10 0 10"
Command="{Binding CustomHotkeyAddCommand}"
Content="{DynamicResource add}" />
</StackPanel>
</StackPanel>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource customQueryShortcut}"
Margin="0 4 0 0"
Icon="&#xf26b;">
<StackPanel>
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="18 12 18 0">
<ListView
MinHeight="160"
Margin="0 6 0 0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding Settings.CustomShortcuts}"
SelectedItem="{Binding SelectedCustomShortcut}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type userSettings:CustomShortcutModel}">
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource customShortcutExpansion}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type userSettings:CustomShortcutModel}">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Command="{Binding CustomShortcutDeleteCommand}"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Command="{Binding CustomShortcutEditCommand}"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10 10 0 10"
Command="{Binding CustomShortcutAddCommand}"
Content="{DynamicResource add}" />
</StackPanel>
</StackPanel>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource builtinShortcuts}"
Margin="0 4 0 14"
Icon="&#xf158;">
<StackPanel>
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="16 8 16 0">
<ListView
MinHeight="160"
Margin="0 6 0 16"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding Settings.BuiltinShortcuts}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type userSettings:BuiltinShortcutModel}">
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource builtinShortcutDescription}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type userSettings:BuiltinShortcutModel}">
<TextBlock
Text="{Binding Description, Converter={StaticResource TranslationConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</StackPanel>
</cc:ExCard>
</StackPanel>
</ScrollViewer>
</ui:Page>

View file

@ -0,0 +1,23 @@
using System;
using System.Windows.Navigation;
using Flow.Launcher.SettingPages.ViewModels;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneHotkey
{
private SettingsPaneHotkeyViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException("Settings are required for SettingsPaneHotkey.");
_viewModel = new SettingsPaneHotkeyViewModel(settings);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
}

View file

@ -0,0 +1,352 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPanePluginStore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="PluginStore"
FocusManager.FocusedElement="{Binding ElementName=PluginStoreFilterTextbox}"
KeyDown="SettingsPanePlugins_OnKeyDown"
d:DataContext="{d:DesignInstance viewModels:SettingsPanePluginStoreViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<ui:Page.Resources>
<CollectionViewSource x:Key="PluginStoreCollectionView" Source="{Binding ExternalPlugins}" Filter="PluginStoreCollectionView_OnFilter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</ui:Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="72" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Padding="5 18 0 0">
<TextBlock
Margin="0 5"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource pluginStore}"
TextAlignment="Left" />
</Border>
<DockPanel
Grid.Row="0"
Grid.Column="1"
Margin="5 24 0 0">
<TextBox
Name="PluginStoreFilterTextbox"
Width="150"
Height="34"
Margin="0 0 26 0"
HorizontalAlignment="Right"
ContextMenu="{StaticResource TextBoxContextMenu}"
DockPanel.Dock="Right"
FontSize="14"
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Left"
ToolTip="{DynamicResource searchpluginToolTip}"
ToolTipService.InitialShowDelay="200"
ToolTipService.Placement="Top">
<TextBox.Style>
<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
<Style.Resources>
<VisualBrush
x:Key="CueBannerBrush"
AlignmentX="Left"
AlignmentY="Center"
Stretch="None">
<VisualBrush.Visual>
<Label
Padding="10 0 0 0"
Content="{DynamicResource searchplugin}"
Foreground="{DynamicResource CustomContextDisabled}" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="{DynamicResource Color02B}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button
Height="34"
Margin="0 5 10 5"
Padding="12 4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding RefreshExternalPluginsCommand}"
Content="{DynamicResource refresh}"
DockPanel.Dock="Right"
FontSize="13" />
</DockPanel>
<ListView
x:Name="StoreListBox"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4 0 0 0"
Padding="0 0 18 0"
FontSize="14"
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">
<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>
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style x:Key="StoreListItemBtnStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Background"
Background="{DynamicResource Color00B}"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1"
CornerRadius="4"
SnapsToDevicePixels="True">
<Border
x:Name="Border"
Padding="{TemplateBinding Padding}"
BorderThickness="1"
CornerRadius="4">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Focusable="False"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Background" Property="Background" Value="{DynamicResource Color07B}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Background" Property="Background" Value="{DynamicResource Color07B}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataTemplate.Resources>
<Button
Name="StoreListItem"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BorderThickness="0"
FocusVisualStyle="{StaticResource StoreItemFocusVisualStyleKey}"
Style="{DynamicResource StoreListItemBtnStyle}">
<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="Hyperlink_OnRequestNavigate">
<Run FontSize="12" Text="{Binding Author, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
<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"
Padding="15 5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Content="{DynamicResource installbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter='!'}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="install" />
<Button
MinHeight="42"
Margin="5 0"
Padding="15 5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{DynamicResource uninstallbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="uninstall" />
<Button
MinHeight="42"
Margin="5 0"
Padding="15 5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{DynamicResource updatebtn}"
Style="{DynamicResource AccentButtonStyle}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="update" />
</VirtualizingStackPanel>
</Grid>
</ui:Flyout>
</ui:FlyoutService.Flyout>
<Grid>
<StackPanel Width="200">
<StackPanel Orientation="Horizontal">
<Image
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="10 24 0 0"
Padding="6 2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#45BD59"
CornerRadius="36"
ToolTip="{DynamicResource LabelUpdateToolTip}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<TextBlock
Margin="18 10 18 0"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
Text="{Binding Name}"
TextWrapping="Wrap"
ToolTip="{Binding Version}" />
<TextBlock
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>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ui:Page>

View file

@ -0,0 +1,66 @@
using System;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Navigation;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePluginStore
{
private SettingsPanePluginStoreViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}.");
_viewModel = new SettingsPanePluginStoreViewModel();
DataContext = _viewModel;
InitializeComponent();
}
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
base.OnNavigatedTo(e);
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText))
{
((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
}
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
base.OnNavigatingFrom(e);
}
private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
PluginStoreFilterTextbox.Focus();
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
PluginManager.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e)
{
if (e.Item is not PluginStoreItemViewModel plugin)
{
e.Accepted = false;
return;
}
e.Accepted = _viewModel.SatisfiesFilter(plugin);
}
}

View file

@ -0,0 +1,102 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPanePlugins"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
Title="Plugins"
FocusManager.FocusedElement="{Binding ElementName=PluginFilterTextbox}"
KeyDown="SettingsPanePlugins_OnKeyDown"
d:DataContext="{d:DesignInstance viewModels:SettingsPanePluginsViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<ui:Page.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</ui:Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="73" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="5 18 0 0">
<TextBlock
Margin="0 5 0 0"
DockPanel.Dock="Left"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource plugins}"
TextAlignment="Left" />
<TextBox
Name="PluginFilterTextbox"
Width="150"
Height="34"
Margin="0 5 26 0"
HorizontalAlignment="Right"
ContextMenu="{StaticResource TextBoxContextMenu}"
DockPanel.Dock="Right"
FontSize="14"
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Left"
ToolTip="{DynamicResource searchpluginToolTip}"
ToolTipService.InitialShowDelay="200"
ToolTipService.Placement="Top">
<TextBox.Style>
<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
<Style.Resources>
<VisualBrush
x:Key="CueBannerBrush"
AlignmentX="Left"
AlignmentY="Center"
Stretch="None">
<VisualBrush.Visual>
<Label
Padding="10 0 0 0"
Content="{DynamicResource searchplugin}"
Foreground="{DynamicResource CustomContextDisabled}" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="{DynamicResource Color02B}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DockPanel>
<Border Grid.Row="1" Grid.Column="0" Background="{DynamicResource Color01B}">
<ListBox
Margin="5 0 7 10"
Background="{DynamicResource Color01B}"
FontSize="14"
ItemsSource="{Binding FilteredPluginViewModels}"
ItemContainerStyle="{StaticResource PluginList}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedPlugin}"
SnapsToDevicePixels="True"
Style="{DynamicResource PluginListStyle}"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemTemplate>
<DataTemplate>
<cc:InstalledPluginDisplay />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</ui:Page>

View file

@ -0,0 +1,30 @@
using System;
using System.Windows.Input;
using System.Windows.Navigation;
using Flow.Launcher.SettingPages.ViewModels;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePlugins
{
private SettingsPanePluginsViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException("Settings are required for SettingsPaneHotkey.");
_viewModel = new SettingsPanePluginsViewModel(settings);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
PluginFilterTextbox.Focus();
}
}

View file

@ -0,0 +1,77 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneProxy"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="Proxy"
d:DataContext="{d:DesignInstance viewModels:SettingsPaneProxyViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary Source="pack://application:,,,/Resources/SettingWindowStyle.xaml" />
</Page.Resources>
<ScrollViewer
Padding="5 0 24 0"
FontSize="14"
CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel>
<TextBlock
Margin="0 23 0 10"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource proxy}"
TextAlignment="left" />
<cc:CardGroup>
<cc:Card Title="{DynamicResource enableProxy}">
<ui:ToggleSwitch IsOn="{Binding Settings.Proxy.Enabled}" />
</cc:Card>
<cc:Card Title="{DynamicResource server}">
<TextBox
Width="300"
IsEnabled="{Binding Settings.Proxy.Enabled}"
Text="{Binding Settings.Proxy.Server}" />
</cc:Card>
<cc:Card Title="{DynamicResource port}">
<TextBox
Width="100"
IsEnabled="{Binding Settings.Proxy.Enabled}"
Text="{Binding Settings.Proxy.Port, TargetNullValue={x:Static sys:String.Empty}}" />
</cc:Card>
<cc:Card Title="{DynamicResource userName}">
<TextBox
Width="200"
IsEnabled="{Binding Settings.Proxy.Enabled}"
Text="{Binding Settings.Proxy.UserName}" />
</cc:Card>
<cc:Card Title="{DynamicResource password}">
<TextBox
Width="200"
IsEnabled="{Binding Settings.Proxy.Enabled}"
Text="{Binding Settings.Proxy.Password}" />
</cc:Card>
</cc:CardGroup>
<cc:Card Title="{DynamicResource testProxy}" Margin="0 8 0 0">
<Button
Width="150"
Content="{DynamicResource testProxy}"
IsEnabled="{Binding Settings.Proxy.Enabled}"
Command="{Binding TestProxyClickedCommand}"/>
</cc:Card>
</StackPanel>
</ScrollViewer>
</ui:Page>

View file

@ -0,0 +1,24 @@
using System;
using System.Windows.Navigation;
using Flow.Launcher.SettingPages.ViewModels;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneProxy
{
private SettingsPaneProxyViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater })
throw new ArgumentException($"Settings are required for {nameof(SettingsPaneProxy)}.");
_viewModel = new SettingsPaneProxyViewModel(settings, updater);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
}

View file

@ -0,0 +1,492 @@
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneTheme"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
xmlns:ext="clr-namespace:Flow.Launcher.Resources.MarkupExtensions"
Title="Theme"
d:DataContext="{d:DesignInstance viewModels:SettingsPaneThemeViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
Style="{DynamicResource SettingPageBasic}"
mc:Ignorable="d">
<ui:Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/SettingWindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<CollectionViewSource x:Key="SortedFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}" />
</ResourceDictionary>
</ui:Page.Resources>
<ScrollViewer
Padding="6 0 24 0"
CanContentScroll="True"
FontSize="14"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel">
<StackPanel>
<!-- Page title -->
<TextBlock
Margin="5 23 0 5"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource appearance}"
TextAlignment="left"
Visibility="Collapsed" />
<!-- Theme preview -->
<StackPanel
Height="350"
Margin="0"
Background="{Binding PreviewBackground}"
IsHitTestVisible="False">
<StackPanel
Margin="0,30,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border
Width="{Binding WindowWidthSize}"
Margin="0"
SnapsToDevicePixels="True"
Style="{DynamicResource WindowBorderStyle}"
UseLayoutRounding="True">
<Border Style="{DynamicResource WindowRadius}">
<Border.Clip>
<MultiBinding Converter="{StaticResource BorderClipConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
<Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Border.Clip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Query TextBox -->
<TextBox
x:Name="QueryTextBox"
Grid.Row="0"
IsReadOnly="True"
Style="{DynamicResource QueryBoxStyle}"
Text="{DynamicResource hiThere}" />
<!-- Clock panel -->
<StackPanel
Grid.Row="0"
Style="{DynamicResource ClockPanel}"
Visibility="Visible">
<!-- Clock TextBox -->
<TextBlock
x:Name="ClockBox"
Style="{DynamicResource ClockBox}"
Text="{Binding ClockText}"
Visibility="{Binding Settings.UseClock, Converter={StaticResource BoolToVisibilityConverter}}" />
<!-- Date TextBox -->
<TextBlock
x:Name="DateBox"
Style="{DynamicResource DateBox}"
Text="{Binding DateText}"
Visibility="{Binding Settings.UseDate, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<Canvas Grid.Row="0" Style="{DynamicResource SearchIconPosition}">
<Path
Margin="0"
Data="{DynamicResource SearchIconImg}"
Stretch="Fill"
Style="{DynamicResource SearchIconStyle}" />
</Canvas>
<Rectangle
Grid.Row="1"
Width="Auto"
HorizontalAlignment="Stretch"
Style="{DynamicResource SeparatorStyle}"
Visibility="Visible" />
<flowlauncher:ResultListBox
Grid.Row="2"
DataContext="{Binding PreviewResults, Mode=OneTime}"
Visibility="Visible" />
</Grid>
</Border>
</Border>
</StackPanel>
</StackPanel>
<!-- Drop shadow effect -->
<cc:Card
Icon="&#xeb91;"
Title="{DynamicResource queryWindowShadowEffect}"
Sub="{DynamicResource shadowEffectCPUUsage}"
Margin="0 4 0 0">
<ui:ToggleSwitch
IsOn="{Binding DropShadowEffect}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<!-- Window width size -->
<cc:Card
Title="{DynamicResource windowWidthSize}"
Icon="&#xe740;"
Sub="{DynamicResource windowWidthSizeToolTip}">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="Auto"
Margin="0 0 8 2"
VerticalAlignment="Center"
Foreground="{DynamicResource Color05B}"
Text="{Binding WindowWidthSize}"
TextAlignment="Right" />
<Slider
Width="250"
VerticalAlignment="Center"
IsMoveToPointEnabled="True"
IsSnapToTickEnabled="True"
Maximum="1920"
Minimum="400"
TickFrequency="10"
Value="{Binding WindowWidthSize}" />
</StackPanel>
</cc:Card>
<!-- Theme -->
<Border
Height="64"
Margin="0 8 0 0"
CornerRadius="5 5 0 0"
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource theme}" />
</StackPanel>
<TextBlock Style="{StaticResource Glyph}">
&#xe790;
</TextBlock>
</ItemsControl>
</Border>
<Border
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
BorderThickness="1 0 1 1"
CornerRadius="0 0 5 5"
Style="{DynamicResource SettingGroupBox}">
<ListBox
Margin="12"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
ui:ScrollViewerHelper.AutoHideScrollBars="True"
Background="#ffffff"
ItemsSource="{Binding Themes}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedTheme}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment"
Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="VerticalContentAlignment"
Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border
x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{DynamicResource Color12B}"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1 1 1 0"
CornerRadius="4"
SnapsToDevicePixels="true">
<Border
x:Name="Bd2"
BorderBrush="{DynamicResource Color14B}"
BorderThickness="0 0 0 2"
CornerRadius="4">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background"
Value="{DynamicResource ThemeHoverButton}" />
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="Background"
Value="{DynamicResource ToggleSwitchFillOn}" />
<Setter TargetName="Bd2" Property="BorderThickness" Value="0" />
<Setter TargetName="Bd2" Property="TextElement.Foreground"
Value="{DynamicResource Color02B}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Template>
<!-- For Scroll Wheel inside listbox area -->
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid
Width="Auto"
Height="34"
Margin="0">
<TextBlock
x:Name="ThemeName"
Margin="0"
Padding="14 12"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Focusable="True"
FontSize="12"
Text="{Binding}"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Border>
<cc:HyperLink
Margin="10"
HorizontalAlignment="Right"
Uri="{Binding LinkThemeGallery}"
Text="{DynamicResource browserMoreThemes}" />
<!-- Fonts and icons -->
<cc:CardGroup Margin="0 30 0 0">
<cc:Card Icon="&#xf6b8;" Title="{DynamicResource useGlyphUI}"
Sub="{DynamicResource useGlyphUIEffect}">
<ui:ToggleSwitch
IsOn="{Binding UseGlyphIcons}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card Icon="&#xe990;" Title="{DynamicResource queryBoxFont}">
<StackPanel Orientation="Horizontal">
<ComboBox
Width="180"
IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding Source={StaticResource SortedFonts}}"
SelectedItem="{Binding SelectedQueryBoxFont}" />
<ComboBox
Width="130"
Margin="10 0 0 0"
ItemsSource="{Binding SelectedQueryBoxFont.FamilyTypefaces}"
SelectedItem="{Binding SelectedQueryBoxFontFaces}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding AdjustedFaceNames}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</cc:Card>
<cc:Card Icon="&#xe8fd;" Title="{DynamicResource resultItemFont}">
<StackPanel Orientation="Horizontal">
<ComboBox
Width="180"
IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding Source={StaticResource SortedFonts}}"
SelectedItem="{Binding SelectedResultFont}" />
<ComboBox
Width="130"
Margin="10 0 0 0"
ItemsSource="{Binding SelectedResultFont.FamilyTypefaces}"
SelectedItem="{Binding SelectedResultFontFaces}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding AdjustedFaceNames}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</cc:Card>
</cc:CardGroup>
<!-- Time and date -->
<cc:CardGroup Margin="0 24 0 0">
<cc:Card Icon="&#xec92;" Title="{DynamicResource Clock}">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color04B}"
Text="{Binding ClockText}" />
<ComboBox
MinWidth="180"
Margin="10 0 18 0"
VerticalAlignment="Center"
FontSize="14"
ItemsSource="{Binding TimeFormatList}"
SelectedItem="{Binding TimeFormat}" />
<ui:ToggleSwitch
IsOn="{Binding UseClock}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</StackPanel>
</cc:Card>
<cc:Card Icon="&#xe787;" Title="{DynamicResource Date}">
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color04B}"
Text="{Binding DateText}" />
<ComboBox
MinWidth="180"
Margin="10 0 18 0"
VerticalAlignment="Center"
FontSize="14"
ItemsSource="{Binding DateFormatList}"
SelectedItem="{Binding DateFormat}" />
<ui:ToggleSwitch
IsOn="{Binding UseDate}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</StackPanel>
</cc:Card>
</cc:CardGroup>
<!-- Animation -->
<cc:CardGroup Margin="0 11 0 0">
<cc:Card
Icon="&#xedb5;"
Title="{DynamicResource Animation}"
Sub="{DynamicResource AnimationTip}">
<ui:ToggleSwitch
IsOn="{Binding UseAnimation}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Icon="&#xe916;"
Title="{DynamicResource AnimationSpeed}"
Sub="{DynamicResource AnimationSpeedTip}"
Visibility="{Binding UseAnimation, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel Orientation="Horizontal">
<ComboBox
MinWidth="160"
VerticalAlignment="Center"
DisplayMemberPath="Display"
FontSize="14"
ItemsSource="{Binding AnimationSpeeds}"
SelectedValue="{Binding Settings.AnimationSpeed}"
SelectedValuePath="Value" />
<TextBox
Margin="18 0 0 0"
MinWidth="80"
Text="{Binding Settings.CustomAnimationLength}"
TextWrapping="NoWrap"
Visibility="{ext:VisibleWhen
{Binding Settings.AnimationSpeed},
IsEqualTo={x:Static userSettings:AnimationSpeeds.Custom}}" />
</StackPanel>
</cc:Card>
</cc:CardGroup>
<!-- SFX -->
<cc:CardGroup Margin="0 11 0 0">
<cc:Card Icon="&#xe7f5;" Title="{DynamicResource SoundEffect}"
Sub="{DynamicResource SoundEffectTip}">
<ui:ToggleSwitch
IsOn="{Binding UseSound}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card Icon="&#xe994;" Title="{DynamicResource SoundEffectVolume}"
Sub="{DynamicResource SoundEffectVolumeTip}"
Visibility="{Binding UseSound, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="0 0 8 0"
VerticalAlignment="Center"
Text="{Binding SoundEffectVolume}" />
<Slider
Width="250"
VerticalAlignment="Center"
IsMoveToPointEnabled="True"
IsSnapToTickEnabled="True"
Maximum="100"
Minimum="0"
TickFrequency="1"
Value="{Binding SoundEffectVolume}" />
</StackPanel>
</cc:Card>
</cc:CardGroup>
<!-- Settings color scheme -->
<cc:Card Icon="&#xe793;" Title="{DynamicResource ColorScheme}" Margin="0 11 0 0">
<ComboBox
MinWidth="180"
DisplayMemberPath="Display"
FontSize="14"
ItemsSource="{Binding ColorSchemes}"
SelectedValue="{Binding Settings.ColorScheme}"
SelectedValuePath="Value"
SelectionChanged="Selector_OnSelectionChanged"/>
</cc:Card>
<!-- Theme folder -->
<cc:Card Icon="&#xe838;" Title="{DynamicResource ThemeFolder}">
<Button
MinWidth="180"
Command="{Binding OpenThemesFolderCommand}"
Content="{DynamicResource OpenThemeFolder}" />
</cc:Card>
<!-- How to create theme link -->
<cc:HyperLink
Margin="10 10 10 28"
HorizontalAlignment="Right"
Uri="{Binding LinkHowToCreateTheme}"
Text="{DynamicResource howToCreateTheme}" />
</StackPanel>
</ScrollViewer>
</ui:Page>

View file

@ -0,0 +1,31 @@
using System;
using System.Windows.Controls;
using System.Windows.Navigation;
using Flow.Launcher.SettingPages.ViewModels;
using Page = ModernWpf.Controls.Page;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneTheme : Page
{
private SettingsPaneThemeViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException($"Settings are required for {nameof(SettingsPaneTheme)}.");
_viewModel = new SettingsPaneThemeViewModel(settings);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewModel.UpdateColorScheme();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,515 +1,172 @@
using Flow.Launcher.Core.Plugin; using System;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using ModernWpf;
using ModernWpf.Controls;
using System;
using System.ComponentModel;
using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Data;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Navigation; using Flow.Launcher.Core;
using NHotkey; using Flow.Launcher.Core.Configuration;
using Button = System.Windows.Controls.Button; using Flow.Launcher.Helper;
using Control = System.Windows.Controls.Control; using Flow.Launcher.Infrastructure.UserSettings;
using KeyEventArgs = System.Windows.Input.KeyEventArgs; using Flow.Launcher.Plugin;
using MessageBox = System.Windows.MessageBox; using Flow.Launcher.SettingPages.Views;
using Flow.Launcher.ViewModel;
using ModernWpf.Controls;
using TextBox = System.Windows.Controls.TextBox; using TextBox = System.Windows.Controls.TextBox;
using ThemeManager = ModernWpf.ThemeManager;
namespace Flow.Launcher namespace Flow.Launcher;
public partial class SettingWindow
{ {
public partial class SettingWindow private readonly IPublicAPI _api;
private readonly Settings _settings;
private readonly SettingWindowViewModel _viewModel;
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
{ {
public readonly IPublicAPI API; _settings = viewModel.Settings;
private Settings settings; DataContext = viewModel;
private SettingWindowViewModel viewModel; _viewModel = viewModel;
_api = api;
InitializePosition();
InitializeComponent();
NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
}
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) private void OnLoaded(object sender, RoutedEventArgs e)
{
RefreshMaximizeRestoreButton();
// Fix (workaround) for the window freezes after lock screen (Win+L)
// https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
HwndTarget hwndTarget = hwndSource.CompositionTarget;
hwndTarget.RenderMode = RenderMode.Default;
InitializePosition();
}
private void OnClosed(object sender, EventArgs e)
{
_settings.SettingWindowState = WindowState;
_settings.SettingWindowTop = Top;
_settings.SettingWindowLeft = Left;
_viewModel.Save();
_api.SavePluginSettings();
}
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
{
if (Keyboard.FocusedElement is not TextBox textBox)
{ {
settings = viewModel.Settings; return;
DataContext = viewModel;
this.viewModel = viewModel;
API = api;
InitializePosition();
InitializeComponent();
} }
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
textBox.MoveFocus(tRequest);
}
#region General /* Custom TitleBar */
private void OnLoaded(object sender, RoutedEventArgs e) private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState switch
{ {
RefreshMaximizeRestoreButton(); WindowState.Maximized => WindowState.Normal,
// Fix (workaround) for the window freezes after lock screen (Win+L) _ => WindowState.Maximized
// https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf };
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; }
HwndTarget hwndTarget = hwndSource.CompositionTarget;
hwndTarget.RenderMode = RenderMode.SoftwareOnly;
pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource); private void OnCloseButtonClick(object sender, RoutedEventArgs e)
pluginListView.Filter = PluginListFilter; {
Close();
}
pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource); private void RefreshMaximizeRestoreButton()
pluginStoreView.Filter = PluginStoreFilter; {
if (WindowState == WindowState.Maximized)
viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged);
CheckMediaPlayer();
InitializePosition();
}
private void CheckMediaPlayer()
{ {
if (settings.WMPInstalled) MaximizeButton.Visibility = Visibility.Collapsed;
{ RestoreButton.Visibility = Visibility.Visible;
WMPWarning.Visibility = Visibility.Visible;
VolumeAdjustCard.Visibility = Visibility.Collapsed;
}
} }
else
private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(viewModel.ExternalPlugins)) MaximizeButton.Visibility = Visibility.Visible;
{ RestoreButton.Visibility = Visibility.Collapsed;
pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
pluginStoreView.Filter = PluginStoreFilter;
pluginStoreView.Refresh();
}
}
private void OnSelectPythonPathClick(object sender, RoutedEventArgs e)
{
var selectedFile = viewModel.GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"),
"Python|pythonw.exe");
if (!string.IsNullOrEmpty(selectedFile))
settings.PluginSettings.PythonExecutablePath = selectedFile;
}
private void OnSelectNodePathClick(object sender, RoutedEventArgs e)
{
var selectedFile = viewModel.GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectNodeExecutable"));
if (!string.IsNullOrEmpty(selectedFile))
settings.PluginSettings.NodeExecutablePath = selectedFile;
}
private void OnSelectFileManagerClick(object sender, RoutedEventArgs e)
{
SelectFileManagerWindow fileManagerChangeWindow = new SelectFileManagerWindow(settings);
fileManagerChangeWindow.ShowDialog();
}
private void OnSelectDefaultBrowserClick(object sender, RoutedEventArgs e)
{
var browserWindow = new SelectBrowserWindow(settings);
browserWindow.ShowDialog();
}
#endregion
#region Hotkey
private void OnToggleHotkey(object sender, HotkeyEventArgs e)
{
HotKeyMapper.OnToggleHotkey(sender, e);
}
private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e)
{
var item = viewModel.SelectedCustomPluginHotkey;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
string deleteWarning =
string.Format(InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"),
item.Hotkey);
if (
MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
settings.CustomPluginHotkeys.Remove(item);
HotKeyMapper.RemoveHotkey(item.Hotkey);
}
}
private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
{
var item = viewModel.SelectedCustomPluginHotkey;
if (item != null)
{
CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, settings);
window.UpdateItem(item);
window.ShowDialog();
}
else
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
}
}
private void OnAddCustomHotkeyClick(object sender, RoutedEventArgs e)
{
new CustomQueryHotkeySetting(this, settings).ShowDialog();
}
#endregion
#region Plugin
private void OnPluginToggled(object sender, RoutedEventArgs e)
{
var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
// used to sync the current status from the plugin manager into the setting to keep consistency after save
settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
}
private void OnPluginPriorityClick(object sender, RoutedEventArgs e)
{
if (sender is Control { DataContext: PluginViewModel pluginViewModel })
{
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, pluginViewModel);
priorityChangeWindow.ShowDialog();
}
}
#endregion
#region Proxy
private void OnTestProxyClick(object sender, RoutedEventArgs e)
{ // TODO: change to command
var msg = viewModel.TestProxy();
MessageBox.Show(msg); // TODO: add message box service
}
#endregion
private void OnCheckUpdates(object sender, RoutedEventArgs e)
{
viewModel.UpdateApp(); // TODO: change to command
}
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void OnClosed(object sender, EventArgs e)
{
settings.SettingWindowState = WindowState;
settings.SettingWindowTop = Top;
settings.SettingWindowLeft = Left;
viewModel.Save();
API.SavePluginSettings();
}
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void OpenThemeFolder(object sender, RoutedEventArgs e)
{
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
}
private void OpenSettingFolder(object sender, RoutedEventArgs e)
{
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
}
private void OpenWelcomeWindow(object sender, RoutedEventArgs e)
{
var WelcomeWindow = new WelcomeWindow(settings);
WelcomeWindow.ShowDialog();
}
private void OpenLogFolder(object sender, RoutedEventArgs e)
{
viewModel.OpenLogFolder();
}
private void ClearLogFolder(object sender, RoutedEventArgs e)
{
var confirmResult = MessageBox.Show(
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
MessageBoxButton.YesNo);
if (confirmResult == MessageBoxResult.Yes)
{
viewModel.ClearLogFolder();
}
}
private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e)
{
if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button)
{
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)
{
if (e.ChangedButton == MouseButton.Left)
{
var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name;
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 */
{
if (Keyboard.FocusedElement is not TextBox textBox)
{
return;
}
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
textBox.MoveFocus(tRequest);
}
private void ColorSchemeSelectedIndexChanged(object sender, EventArgs e)
=> ThemeManager.Current.ApplicationTheme = settings.ColorScheme switch
{
Constant.Light => ApplicationTheme.Light,
Constant.Dark => ApplicationTheme.Dark,
Constant.System => null,
_ => ThemeManager.Current.ApplicationTheme
};
/* Custom TitleBar */
private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
{
Close();
}
private void RefreshMaximizeRestoreButton()
{
if (WindowState == WindowState.Maximized)
{
maximizeButton.Visibility = Visibility.Collapsed;
restoreButton.Visibility = Visibility.Visible;
}
else
{
maximizeButton.Visibility = Visibility.Visible;
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(); Should Fix
}
}
private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e)
{
viewModel.AddCustomShortcut();
}
#endregion
private CollectionView pluginListView;
private CollectionView pluginStoreView;
private bool PluginListFilter(object item)
{
if (string.IsNullOrEmpty(pluginFilterTxb.Text))
return true;
if (item is PluginViewModel model)
{
return StringMatcher.FuzzySearch(pluginFilterTxb.Text, model.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet();
}
return false;
}
private bool PluginStoreFilter(object item)
{
if (string.IsNullOrEmpty(pluginStoreFilterTxb.Text))
return true;
if (item is PluginStoreItemViewModel model)
{
return StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Name).IsSearchPrecisionScoreMet()
|| StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Description).IsSearchPrecisionScoreMet();
}
return false;
}
private string lastPluginListSearch = "";
private string lastPluginStoreSearch = "";
private void RefreshPluginListEventHandler(object sender, RoutedEventArgs e)
{
if (pluginFilterTxb.Text != lastPluginListSearch)
{
lastPluginListSearch = pluginFilterTxb.Text;
pluginListView.Refresh();
}
}
private void RefreshPluginStoreEventHandler(object sender, RoutedEventArgs e)
{
if (pluginStoreFilterTxb.Text != lastPluginStoreSearch)
{
lastPluginStoreSearch = pluginStoreFilterTxb.Text;
pluginStoreView.Refresh();
}
}
private void PluginFilterTxb_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
RefreshPluginListEventHandler(sender, e);
}
private void PluginStoreFilterTxb_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
RefreshPluginStoreEventHandler(sender, e);
}
private void OnPluginSettingKeydown(object sender, KeyEventArgs e)
{
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && e.Key == Key.F)
pluginFilterTxb.Focus();
}
private void PluginStore_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F && (Keyboard.Modifiers & ModifierKeys.Control) != 0)
{
pluginStoreFilterTxb.Focus();
}
}
public void InitializePosition()
{
if (settings.SettingWindowTop >= 0 && settings.SettingWindowLeft >= 0)
{
Top = settings.SettingWindowTop;
Left = settings.SettingWindowLeft;
}
else
{
Top = WindowTop();
Left = WindowLeft();
}
WindowState = settings.SettingWindowState;
}
public double WindowLeft()
{
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - this.ActualWidth) / 2 + dip1.X;
return left;
}
public double WindowTop()
{
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20;
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;
};
}
private void PluginStore_GotFocus(object sender, RoutedEventArgs e)
{
Keyboard.Focus(pluginStoreFilterTxb);
}
private void Plugin_GotFocus(object sender, RoutedEventArgs e)
{
Keyboard.Focus(pluginFilterTxb);
} }
} }
private void Window_StateChanged(object sender, EventArgs e)
{
RefreshMaximizeRestoreButton();
}
public void InitializePosition()
{
if (_settings.SettingWindowTop == null)
{
Top = WindowTop();
Left = WindowLeft();
}
else
{
Top = _settings.SettingWindowTop;
Left = _settings.SettingWindowLeft;
}
WindowState = _settings.SettingWindowState;
}
private double WindowLeft()
{
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - this.ActualWidth) / 2 + dip1.X;
return left;
}
private double WindowTop()
{
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20;
return top;
}
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable);
if (args.IsSettingsSelected)
{
ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData);
}
else
{
var selectedItem = (NavigationViewItem)args.SelectedItem;
if (selectedItem == null) return;
var pageType = selectedItem.Name switch
{
nameof(General) => typeof(SettingsPaneGeneral),
nameof(Plugins) => typeof(SettingsPanePlugins),
nameof(PluginStore) => typeof(SettingsPanePluginStore),
nameof(Theme) => typeof(SettingsPaneTheme),
nameof(Hotkey) => typeof(SettingsPaneHotkey),
nameof(Proxy) => typeof(SettingsPaneProxy),
nameof(About) => typeof(SettingsPaneAbout),
_ => typeof(SettingsPaneGeneral)
};
ContentFrame.Navigate(pageType, paneData);
}
}
public record PaneData(Settings Settings, Updater Updater, IPortable Portable);
} }

View file

@ -42,6 +42,7 @@ namespace Flow.Launcher.ViewModel
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage; private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
private readonly FlowLauncherJsonStorage<TopMostRecord> _topMostRecordStorage; private readonly FlowLauncherJsonStorage<TopMostRecord> _topMostRecordStorage;
private readonly History _history; private readonly History _history;
private int lastHistoryIndex = 1;
private readonly UserSelectedRecord _userSelectedRecord; private readonly UserSelectedRecord _userSelectedRecord;
private readonly TopMostRecord _topMostRecord; private readonly TopMostRecord _topMostRecord;
@ -83,6 +84,12 @@ namespace Flow.Launcher.ViewModel
case nameof(Settings.AutoCompleteHotkey): case nameof(Settings.AutoCompleteHotkey):
OnPropertyChanged(nameof(AutoCompleteHotkey)); OnPropertyChanged(nameof(AutoCompleteHotkey));
break; break;
case nameof(Settings.CycleHistoryUpHotkey):
OnPropertyChanged(nameof(CycleHistoryUpHotkey));
break;
case nameof(Settings.CycleHistoryDownHotkey):
OnPropertyChanged(nameof(CycleHistoryDownHotkey));
break;
case nameof(Settings.AutoCompleteHotkey2): case nameof(Settings.AutoCompleteHotkey2):
OnPropertyChanged(nameof(AutoCompleteHotkey2)); OnPropertyChanged(nameof(AutoCompleteHotkey2));
break; break;
@ -256,6 +263,32 @@ namespace Flow.Launcher.ViewModel
} }
} }
[RelayCommand]
public void ReverseHistory()
{
if (_history.Items.Count > 0)
{
ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString());
if (lastHistoryIndex < _history.Items.Count)
{
lastHistoryIndex++;
}
}
}
[RelayCommand]
public void ForwardHistory()
{
if (_history.Items.Count > 0)
{
ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString());
if (lastHistoryIndex > 1)
{
lastHistoryIndex--;
}
}
}
[RelayCommand] [RelayCommand]
private void LoadContextMenu() private void LoadContextMenu()
{ {
@ -346,6 +379,7 @@ namespace Flow.Launcher.ViewModel
{ {
_userSelectedRecord.Add(result); _userSelectedRecord.Add(result);
_history.Add(result.OriginQuery.RawQuery); _history.Add(result.OriginQuery.RawQuery);
lastHistoryIndex = 1;
} }
if (hideWindow) if (hideWindow)
@ -394,7 +428,18 @@ namespace Flow.Launcher.ViewModel
[RelayCommand] [RelayCommand]
private void SelectPrevItem() private void SelectPrevItem()
{ {
SelectedResults.SelectPrevResult(); if (_history.Items.Count > 0
&& QueryText == string.Empty
&& SelectedIsFromQueryResults())
{
lastHistoryIndex = 1;
ReverseHistory();
}
else
{
SelectedResults.SelectPrevResult();
}
} }
[RelayCommand] [RelayCommand]
@ -690,6 +735,8 @@ namespace Flow.Launcher.ViewModel
public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, ""); public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, "");
public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O"); public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O");
public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I"); public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I");
public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up");
public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down");
public string Image => Constant.QueryTextBoxIconImagePath; public string Image => Constant.QueryTextBoxIconImagePath;
@ -1116,6 +1163,7 @@ namespace Flow.Launcher.ViewModel
public async void Hide() public async void Hide()
{ {
lastHistoryIndex = 1;
// Trick for no delay // Trick for no delay
MainWindowOpacity = 0; MainWindowOpacity = 0;
lastContextMenuResult = new Result(); lastContextMenuResult = new Result();

View file

@ -1,12 +1,15 @@
using System; using System;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
public class PluginStoreItemViewModel : BaseModel public partial class PluginStoreItemViewModel : BaseModel
{ {
private PluginPair PluginManagerData => PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7");
public PluginStoreItemViewModel(UserPlugin plugin) public PluginStoreItemViewModel(UserPlugin plugin)
{ {
_plugin = plugin; _plugin = plugin;
@ -26,19 +29,13 @@ namespace Flow.Launcher.ViewModel
public string IcoPath => _plugin.IcoPath; public string IcoPath => _plugin.IcoPath;
public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null; public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null;
public bool LabelUpdate => LabelInstalled && VersionConvertor(_plugin.Version) > VersionConvertor(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version); public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
internal const string None = "None"; internal const string None = "None";
internal const string RecentlyUpdated = "RecentlyUpdated"; internal const string RecentlyUpdated = "RecentlyUpdated";
internal const string NewRelease = "NewRelease"; internal const string NewRelease = "NewRelease";
internal const string Installed = "Installed"; internal const string Installed = "Installed";
public Version VersionConvertor(string version)
{
Version ResultVersion = new Version(version);
return ResultVersion;
}
public string Category public string Category
{ {
get get
@ -60,5 +57,13 @@ namespace Flow.Launcher.ViewModel
return category; return category;
} }
} }
[RelayCommand]
private void ShowCommandQuery(string action)
{
var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty;
App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}");
App.API.ShowMainWindow();
}
} }
} }

View file

@ -1,4 +1,5 @@
using System.Windows; using System.Linq;
using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
@ -26,6 +27,21 @@ namespace Flow.Launcher.ViewModel
} }
} }
private string PluginManagerActionKeyword
{
get
{
var keyword = PluginManager
.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")
.Metadata.ActionKeywords.FirstOrDefault();
return keyword switch
{
null or "*" => string.Empty,
_ => keyword
};
}
}
private async void LoadIconAsync() private async void LoadIconAsync()
{ {
@ -46,7 +62,11 @@ namespace Flow.Launcher.ViewModel
public bool PluginState public bool PluginState
{ {
get => !PluginPair.Metadata.Disabled; get => !PluginPair.Metadata.Disabled;
set => PluginPair.Metadata.Disabled = !value; set
{
PluginPair.Metadata.Disabled = !value;
PluginSettingsObject.Disabled = !value;
}
} }
public bool IsExpanded public bool IsExpanded
{ {
@ -62,11 +82,13 @@ namespace Flow.Launcher.ViewModel
private Control _settingControl; private Control _settingControl;
private bool _isExpanded; private bool _isExpanded;
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider;
public Control SettingControl public Control SettingControl
=> IsExpanded => IsExpanded
? _settingControl ? _settingControl
??= PluginPair.Plugin is not ISettingProvider settingProvider ??= PluginPair.Plugin is not ISettingProvider settingProvider
? new Control() ? null
: settingProvider.CreateSettingPanel() : settingProvider.CreateSettingPanel()
: null; : null;
private ImageSource _image = ImageLoader.MissingImage; private ImageSource _image = ImageLoader.MissingImage;
@ -78,6 +100,7 @@ namespace Flow.Launcher.ViewModel
public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public int Priority => PluginPair.Metadata.Priority; public int Priority => PluginPair.Metadata.Priority;
public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; }
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
{ {
@ -88,13 +111,14 @@ namespace Flow.Launcher.ViewModel
public void ChangePriority(int newPriority) public void ChangePriority(int newPriority)
{ {
PluginPair.Metadata.Priority = newPriority; PluginPair.Metadata.Priority = newPriority;
PluginSettingsObject.Priority = newPriority;
OnPropertyChanged(nameof(Priority)); OnPropertyChanged(nameof(Priority));
} }
[RelayCommand] [RelayCommand]
private void EditPluginPriority() private void EditPluginPriority()
{ {
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this); PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
priorityChangeWindow.ShowDialog(); priorityChangeWindow.ShowDialog();
} }
@ -106,6 +130,19 @@ namespace Flow.Launcher.ViewModel
PluginManager.API.OpenDirectory(directory); PluginManager.API.OpenDirectory(directory);
} }
[RelayCommand]
private void OpenSourceCodeLink()
{
PluginManager.API.OpenUrl(PluginPair.Metadata.Website);
}
[RelayCommand]
private void OpenDeletePluginWindow()
{
PluginManager.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true);
PluginManager.API.ShowMainWindow();
}
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
[RelayCommand] [RelayCommand]

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -26,7 +26,6 @@
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String> <system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_prompt_no_restart">{0} by {1} {2}{2}Would you like to update this plugin?</system:String> <system:String x:Key="plugin_pluginsmanager_update_prompt_no_restart">{0} by {1} {2}{2}Would you like to update this plugin?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String> <system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String>
<system:String x:Key="plugin_pluginsmanager_update_exists">This plugin has an update, would you like to see it?</system:String>
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String> <system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
<system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String> <system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String>
<system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String> <system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String>

View file

@ -3,11 +3,9 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -99,13 +97,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Context.API.GetAllPlugins() if (Context.API.GetAllPlugins()
.Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0))
{ {
if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath;
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes) Context
Context .API
.API .ChangeQuery(
.ChangeQuery( $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}");
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}");
var mainWindow = Application.Current.MainWindow; var mainWindow = Application.Current.MainWindow;
mainWindow.Show(); mainWindow.Show();
@ -147,12 +144,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
try try
{ {
if (File.Exists(filePath)) if (!plugin.IsFromLocalInstallPath)
{ {
File.Delete(filePath); if (File.Exists(filePath))
} File.Delete(filePath);
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
}
else
{
filePath = plugin.LocalInstallPath;
}
Install(plugin, filePath); Install(plugin, filePath);
} }
@ -193,24 +195,38 @@ namespace Flow.Launcher.Plugin.PluginsManager
{ {
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
var pluginFromLocalPath = null as UserPlugin;
var updateFromLocalPath = false;
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
{
pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search);
pluginFromLocalPath.LocalInstallPath = search;
updateFromLocalPath = true;
}
var updateSource = !updateFromLocalPath
? PluginsManifest.UserPlugins
: new List<UserPlugin> { pluginFromLocalPath };
var resultsForUpdate = ( var resultsForUpdate = (
from existingPlugin in Context.API.GetAllPlugins() from existingPlugin in Context.API.GetAllPlugins()
join pluginFromManifest in PluginsManifest.UserPlugins join pluginUpdateSource in updateSource
on existingPlugin.Metadata.ID equals pluginFromManifest.ID on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version, where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) < StringComparison.InvariantCulture) <
0 // if current version precedes manifest version 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID) && !PluginManager.PluginModified(existingPlugin.Metadata.ID)
select select
new new
{ {
pluginFromManifest.Name, pluginUpdateSource.Name,
pluginFromManifest.Author, pluginUpdateSource.Author,
CurrentVersion = existingPlugin.Metadata.Version, CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginFromManifest.Version, NewVersion = pluginUpdateSource.Version,
existingPlugin.Metadata.IcoPath, existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata, PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginFromManifest PluginNewUserPlugin = pluginUpdateSource
}).ToList(); }).ToList();
if (!resultsForUpdate.Any()) if (!resultsForUpdate.Any())
@ -261,13 +277,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
_ = Task.Run(async delegate _ = Task.Run(async delegate
{ {
if (File.Exists(downloadToFilePath)) if (!x.PluginNewUserPlugin.IsFromLocalInstallPath)
{ {
File.Delete(downloadToFilePath); if (File.Exists(downloadToFilePath))
{
File.Delete(downloadToFilePath);
}
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
}
else
{
downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath;
} }
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin,
downloadToFilePath); downloadToFilePath);
@ -396,7 +420,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
results = results.Prepend(updateAllResult); results = results.Prepend(updateAllResult);
} }
return Search(results, search); return !updateFromLocalPath ? Search(results, search) : results.ToList();
} }
internal bool PluginExists(string id) internal bool PluginExists(string id)
@ -470,6 +494,42 @@ namespace Flow.Launcher.Plugin.PluginsManager
return new List<Result> { result }; return new List<Result> { result };
} }
internal List<Result> InstallFromLocalPath(string localPath)
{
var plugin = Utilities.GetPluginInfoFromZip(localPath);
plugin.LocalInstallPath = localPath;
return new List<Result>
{
new Result
{
Title = $"{plugin.Name} by {plugin.Author}",
SubTitle = plugin.Description,
IcoPath = plugin.IcoPath,
Action = e =>
{
if (Settings.WarnFromUnknownSource)
{
if (!InstallSourceKnown(plugin.Website)
&& MessageBox.Show(string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
Environment.NewLine),
Context.API.GetTranslation(
"plugin_pluginsmanager_install_unknown_source_warning_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return false;
}
Application.Current.MainWindow.Hide();
_ = InstallOrUpdateAsync(plugin);
return ShouldHideWindow;
}
}
};
}
private bool InstallSourceKnown(string url) private bool InstallSourceKnown(string url)
{ {
var author = url.Split('/')[3]; var author = url.Split('/')[3];
@ -489,6 +549,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
&& search.Split('.').Last() == zip) && search.Split('.').Last() == zip)
return InstallFromWeb(search); return InstallFromWeb(search);
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
return InstallFromLocalPath(search);
var results = var results =
PluginsManifest PluginsManifest
.UserPlugins .UserPlugins
@ -522,10 +585,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (!File.Exists(downloadedFilePath)) if (!File.Exists(downloadedFilePath))
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}",
downloadedFilePath); downloadedFilePath);
try try
{ {
PluginManager.InstallPlugin(plugin, downloadedFilePath); PluginManager.InstallPlugin(plugin, downloadedFilePath);
File.Delete(downloadedFilePath);
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {

View file

@ -1,5 +1,10 @@
using ICSharpCode.SharpZipLib.Zip; using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure.UserSettings;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq;
namespace Flow.Launcher.Plugin.PluginsManager namespace Flow.Launcher.Plugin.PluginsManager
{ {
@ -55,5 +60,28 @@ namespace Flow.Launcher.Plugin.PluginsManager
return string.Empty; return string.Empty;
} }
internal static UserPlugin GetPluginInfoFromZip(string filePath)
{
var plugin = null as UserPlugin;
using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath))
{
var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString();
ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath);
if (pluginJsonEntry != null)
{
using (StreamReader reader = new StreamReader(pluginJsonEntry.Open()))
{
string pluginJsonContent = reader.ReadToEnd();
plugin = JsonConvert.DeserializeObject<UserPlugin>(pluginJsonContent);
plugin.IcoPath = "Images\\zipfolder.png";
}
}
}
return plugin;
}
} }
} }