- 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
{
@ -13,9 +13,11 @@ namespace Flow.Launcher.Core.ExternalPlugins
public string Website { get; set; }
public string UrlDownload { get; set; }
public string UrlSourceCode { get; set; }
public string LocalInstallPath { get; set; }
public string IcoPath { get; set; }
public DateTime? LatestReleaseDate { 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>
/// 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>
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
@ -390,11 +391,11 @@ namespace Flow.Launcher.Core.Plugin
}
/// <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>
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{
InstallPlugin(plugin, zipFilePath, true);
InstallPlugin(plugin, zipFilePath, checkModified: true);
}
/// <summary>
@ -420,7 +421,9 @@ namespace Flow.Launcher.Core.Plugin
// Unzip plugin files to temp folder
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
File.Delete(zipFilePath);
if(!plugin.IsFromLocalInstallPath)
File.Delete(zipFilePath);
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);

View file

@ -31,6 +31,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public string SelectPrevPageHotkey { get; set; } = $"PageDown";
public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O";
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
{
@ -283,42 +285,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
get
{
var list = 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)
};
var list = FixedHotkeys();
// Customizeable hotkeys
if(!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if(!string.IsNullOrEmpty(PreviewHotkey))
@ -343,7 +312,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
if(!string.IsNullOrEmpty(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)
{
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
@ -353,6 +327,45 @@ namespace Flow.Launcher.Infrastructure.UserSettings
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

View file

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
#pragma warning disable IDE0005
using System.Windows;
#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>
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.

View file

@ -191,11 +191,15 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"\c:\", false)]
[TestCase(@"cc:\", false)]
[TestCase(@"\\\SomeNetworkLocation\", false)]
[TestCase(@"\\SomeNetworkLocation\", true)]
[TestCase("RandomFile", false)]
[TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", 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)
{
// When, Given

View file

@ -24,6 +24,7 @@
</ui:ThemeResources>
<ui:XamlControlsResources />
<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:,,,/Languages/en.xaml" />
</ResourceDictionary.MergedDictionaries>

View file

@ -8,7 +8,6 @@ namespace Flow.Launcher
{
public partial class CustomShortcutSetting : Window
{
private SettingWindowViewModel viewModel;
public string Key { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty;
private string originalKey { get; init; } = null;
@ -17,13 +16,11 @@ namespace Flow.Launcher
public CustomShortcutSetting(SettingWindowViewModel vm)
{
viewModel = vm;
InitializeComponent();
}
public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm)
public CustomShortcutSetting(string key, string value)
{
viewModel = vm;
Key = key;
Value = value;
originalKey = key;
@ -46,8 +43,7 @@ namespace Flow.Launcher
return;
}
// Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update)
&& viewModel.ShortcutExists(Key))
if ((update && originalKey != Key) || !update)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
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="SelectNextPageHotkey">Next 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="SettingWindowHotkey">Open Setting Window</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'}"
Command="{Binding SelectPrevPageCommand}"
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>
<Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">

View file

@ -20,6 +20,7 @@
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="MinHeight" Value="68" />
<Setter Property="Padding" Value="0,15,0,15" />
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Style.Triggers>
@ -36,6 +37,27 @@
<Setter Property="Padding" Value="38,0,26,0" />
<Setter Property="Background" Value="Transparent" />
</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>
</Border.Style>

View file

@ -16,6 +16,7 @@ namespace Flow.Launcher.Resources.Controls
{
InitializeComponent();
}
public string Title
{
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 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;
using System.Windows;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Navigation;
using NHotkey;
using Button = System.Windows.Controls.Button;
using Control = System.Windows.Controls.Control;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MessageBox = System.Windows.MessageBox;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.SettingPages.Views;
using Flow.Launcher.ViewModel;
using ModernWpf.Controls;
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;
private Settings settings;
private SettingWindowViewModel viewModel;
_settings = viewModel.Settings;
DataContext = 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;
DataContext = viewModel;
this.viewModel = viewModel;
API = api;
InitializePosition();
InitializeComponent();
return;
}
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();
// 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.SoftwareOnly;
WindowState.Maximized => WindowState.Normal,
_ => WindowState.Maximized
};
}
pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource);
pluginListView.Filter = PluginListFilter;
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
{
Close();
}
pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
pluginStoreView.Filter = PluginStoreFilter;
viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged);
CheckMediaPlayer();
InitializePosition();
}
private void CheckMediaPlayer()
private void RefreshMaximizeRestoreButton()
{
if (WindowState == WindowState.Maximized)
{
if (settings.WMPInstalled)
{
WMPWarning.Visibility = Visibility.Visible;
VolumeAdjustCard.Visibility = Visibility.Collapsed;
}
MaximizeButton.Visibility = Visibility.Collapsed;
RestoreButton.Visibility = Visibility.Visible;
}
private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e)
else
{
if (e.PropertyName == nameof(viewModel.ExternalPlugins))
{
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);
MaximizeButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
}
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<TopMostRecord> _topMostRecordStorage;
private readonly History _history;
private int lastHistoryIndex = 1;
private readonly UserSelectedRecord _userSelectedRecord;
private readonly TopMostRecord _topMostRecord;
@ -83,6 +84,12 @@ namespace Flow.Launcher.ViewModel
case nameof(Settings.AutoCompleteHotkey):
OnPropertyChanged(nameof(AutoCompleteHotkey));
break;
case nameof(Settings.CycleHistoryUpHotkey):
OnPropertyChanged(nameof(CycleHistoryUpHotkey));
break;
case nameof(Settings.CycleHistoryDownHotkey):
OnPropertyChanged(nameof(CycleHistoryDownHotkey));
break;
case nameof(Settings.AutoCompleteHotkey2):
OnPropertyChanged(nameof(AutoCompleteHotkey2));
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]
private void LoadContextMenu()
{
@ -346,6 +379,7 @@ namespace Flow.Launcher.ViewModel
{
_userSelectedRecord.Add(result);
_history.Add(result.OriginQuery.RawQuery);
lastHistoryIndex = 1;
}
if (hideWindow)
@ -394,7 +428,18 @@ namespace Flow.Launcher.ViewModel
[RelayCommand]
private void SelectPrevItem()
{
SelectedResults.SelectPrevResult();
if (_history.Items.Count > 0
&& QueryText == string.Empty
&& SelectedIsFromQueryResults())
{
lastHistoryIndex = 1;
ReverseHistory();
}
else
{
SelectedResults.SelectPrevResult();
}
}
[RelayCommand]
@ -690,6 +735,8 @@ namespace Flow.Launcher.ViewModel
public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, "");
public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O");
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;
@ -1116,6 +1163,7 @@ namespace Flow.Launcher.ViewModel
public async void Hide()
{
lastHistoryIndex = 1;
// Trick for no delay
MainWindowOpacity = 0;
lastContextMenuResult = new Result();

View file

@ -1,12 +1,15 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
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)
{
_plugin = plugin;
@ -26,19 +29,13 @@ namespace Flow.Launcher.ViewModel
public string IcoPath => _plugin.IcoPath;
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 RecentlyUpdated = "RecentlyUpdated";
internal const string NewRelease = "NewRelease";
internal const string Installed = "Installed";
public Version VersionConvertor(string version)
{
Version ResultVersion = new Version(version);
return ResultVersion;
}
public string Category
{
get
@ -60,5 +57,13 @@ namespace Flow.Launcher.ViewModel
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 Flow.Launcher.Plugin;
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()
{
@ -46,7 +62,11 @@ namespace Flow.Launcher.ViewModel
public bool PluginState
{
get => !PluginPair.Metadata.Disabled;
set => PluginPair.Metadata.Disabled = !value;
set
{
PluginPair.Metadata.Disabled = !value;
PluginSettingsObject.Disabled = !value;
}
}
public bool IsExpanded
{
@ -62,11 +82,13 @@ namespace Flow.Launcher.ViewModel
private Control _settingControl;
private bool _isExpanded;
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider;
public Control SettingControl
=> IsExpanded
? _settingControl
??= PluginPair.Plugin is not ISettingProvider settingProvider
? new Control()
? null
: settingProvider.CreateSettingPanel()
: null;
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 ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public int Priority => PluginPair.Metadata.Priority;
public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; }
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
{
@ -88,13 +111,14 @@ namespace Flow.Launcher.ViewModel
public void ChangePriority(int newPriority)
{
PluginPair.Metadata.Priority = newPriority;
PluginSettingsObject.Priority = newPriority;
OnPropertyChanged(nameof(Priority));
}
[RelayCommand]
private void EditPluginPriority()
{
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this);
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
priorityChangeWindow.ShowDialog();
}
@ -106,6 +130,19 @@ namespace Flow.Launcher.ViewModel
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);
[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_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_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_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>

View file

@ -3,11 +3,9 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -99,13 +97,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (Context.API.GetAllPlugins()
.Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0))
{
if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"),
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
Context
.API
.ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}");
var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath;
Context
.API
.ChangeQuery(
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}");
var mainWindow = Application.Current.MainWindow;
mainWindow.Show();
@ -147,12 +144,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
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);
}
@ -193,24 +195,38 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
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 = (
from existingPlugin in Context.API.GetAllPlugins()
join pluginFromManifest in PluginsManifest.UserPlugins
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version,
join pluginUpdateSource in updateSource
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
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)
select
new
{
pluginFromManifest.Name,
pluginFromManifest.Author,
pluginUpdateSource.Name,
pluginUpdateSource.Author,
CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginFromManifest.Version,
NewVersion = pluginUpdateSource.Version,
existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginFromManifest
PluginNewUserPlugin = pluginUpdateSource
}).ToList();
if (!resultsForUpdate.Any())
@ -261,13 +277,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
_ = 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);
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
}
else
{
downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath;
}
PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin,
downloadToFilePath);
@ -396,7 +420,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
results = results.Prepend(updateAllResult);
}
return Search(results, search);
return !updateFromLocalPath ? Search(results, search) : results.ToList();
}
internal bool PluginExists(string id)
@ -470,6 +494,42 @@ namespace Flow.Launcher.Plugin.PluginsManager
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)
{
var author = url.Split('/')[3];
@ -489,6 +549,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
&& search.Split('.').Last() == zip)
return InstallFromWeb(search);
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
return InstallFromLocalPath(search);
var results =
PluginsManifest
.UserPlugins
@ -522,10 +585,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (!File.Exists(downloadedFilePath))
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}",
downloadedFilePath);
try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
File.Delete(downloadedFilePath);
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
}
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.Compression;
using System.Linq;
namespace Flow.Launcher.Plugin.PluginsManager
{
@ -55,5 +60,28 @@ namespace Flow.Launcher.Plugin.PluginsManager
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;
}
}
}