Skip to content

Commit 9a6eb36

Browse files
committed
Add context menu result for plugin settings, which calls new public api
OpenPluginSettingsWindow, which opens new PluginSettingsWindow that holds an InstalledPluginDisplay
1 parent 0a1fc56 commit 9a6eb36

5 files changed

Lines changed: 347 additions & 0 deletions

File tree

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ public interface IPublicAPI
162162
/// </summary>
163163
void OpenSettingDialog();
164164

165+
166+
/// <summary>
167+
/// Open plugin setting window for a specific plugin
168+
/// </summary>
169+
/// <param name="pluginId">ID of the plugin whose settings window should be opened</param>
170+
void OpenPluginSettingsWindow(string pluginId);
171+
165172
/// <summary>
166173
/// Get translation of current language
167174
/// You need to implement IPluginI18n if you want to support multiple languages for your plugin
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<Window
2+
x:Class="Flow.Launcher.PluginSettingsWindow"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
6+
Width="920"
7+
Height="320"
8+
MinWidth="720"
9+
MinHeight="230"
10+
ResizeMode="CanResize"
11+
SnapsToDevicePixels="True"
12+
Loaded="OnLoaded"
13+
StateChanged="Window_StateChanged"
14+
UseLayoutRounding="True"
15+
WindowStartupLocation="CenterScreen">
16+
<WindowChrome.WindowChrome>
17+
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
18+
</WindowChrome.WindowChrome>
19+
<Window.InputBindings>
20+
<KeyBinding Key="Escape" Command="Close" />
21+
</Window.InputBindings>
22+
<Window.CommandBindings>
23+
<CommandBinding Command="Close" Executed="OnCloseExecuted" />
24+
</Window.CommandBindings>
25+
26+
<Grid>
27+
<Border Style="{StaticResource WindowMainPanelStyle}">
28+
<Grid Background="{DynamicResource Color01B}">
29+
<Grid.ColumnDefinitions>
30+
<ColumnDefinition Width="Auto" />
31+
<ColumnDefinition Width="*" />
32+
<ColumnDefinition Width="Auto" />
33+
<ColumnDefinition Width="Auto" />
34+
<ColumnDefinition Width="Auto" />
35+
</Grid.ColumnDefinitions>
36+
<Grid.RowDefinitions>
37+
<RowDefinition Height="32" />
38+
<RowDefinition Height="*" />
39+
</Grid.RowDefinitions>
40+
41+
<Image
42+
Grid.Row="0"
43+
Grid.Column="0"
44+
Width="16"
45+
Height="16"
46+
Margin="10 4 4 4"
47+
RenderOptions.BitmapScalingMode="HighQuality"
48+
Source="/Images/app.png" />
49+
<TextBlock
50+
Grid.Row="0"
51+
Grid.Column="1"
52+
Margin="4 0 0 0"
53+
VerticalAlignment="Center"
54+
FontSize="12"
55+
Foreground="{DynamicResource Color05B}"
56+
Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=Title}" />
57+
58+
<Button
59+
Grid.Row="0"
60+
Grid.Column="2"
61+
Click="OnMinimizeButtonClick"
62+
RenderOptions.EdgeMode="Aliased"
63+
Style="{DynamicResource TitleBarButtonStyle}">
64+
<Path
65+
Width="46"
66+
Height="32"
67+
Data="M 18,15 H 28"
68+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
69+
StrokeThickness="1">
70+
<Path.Style>
71+
<Style TargetType="Path">
72+
<Style.Triggers>
73+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
74+
<Setter Property="Opacity" Value="0.5" />
75+
</DataTrigger>
76+
</Style.Triggers>
77+
</Style>
78+
</Path.Style>
79+
</Path>
80+
</Button>
81+
<Button
82+
Name="MaximizeButton"
83+
Grid.Row="0"
84+
Grid.Column="3"
85+
Click="OnMaximizeRestoreButtonClick"
86+
Style="{StaticResource TitleBarButtonStyle}">
87+
<Path
88+
Width="46"
89+
Height="32"
90+
Data="M 18.5,10.5 H 27.5 V 19.5 H 18.5 Z"
91+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
92+
StrokeThickness="1">
93+
<Path.Style>
94+
<Style TargetType="Path">
95+
<Style.Triggers>
96+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
97+
<Setter Property="Opacity" Value="0.5" />
98+
</DataTrigger>
99+
</Style.Triggers>
100+
</Style>
101+
</Path.Style>
102+
</Path>
103+
</Button>
104+
<Button
105+
Grid.Row="0"
106+
Name="RestoreButton"
107+
Grid.Column="3"
108+
Click="OnMaximizeRestoreButtonClick"
109+
Style="{StaticResource TitleBarButtonStyle}">
110+
<Path
111+
Width="46"
112+
Height="32"
113+
Data="M 18.5,12.5 H 25.5 V 19.5 H 18.5 Z M 20.5,12.5 V 10.5 H 27.5 V 17.5 H 25.5"
114+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
115+
StrokeThickness="1">
116+
<Path.Style>
117+
<Style TargetType="Path">
118+
<Style.Triggers>
119+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
120+
<Setter Property="Opacity" Value="0.5" />
121+
</DataTrigger>
122+
</Style.Triggers>
123+
</Style>
124+
</Path.Style>
125+
</Path>
126+
</Button>
127+
<Button
128+
Grid.Row="0"
129+
Grid.Column="4"
130+
Click="OnCloseButtonClick"
131+
Style="{StaticResource TitleBarCloseButtonStyle}">
132+
<Path
133+
Width="46"
134+
Height="32"
135+
Data="M 18,11 27,20 M 18,20 27,11"
136+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
137+
StrokeThickness="1">
138+
<Path.Style>
139+
<Style TargetType="Path">
140+
<Style.Triggers>
141+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
142+
<Setter Property="Opacity" Value="0.5" />
143+
</DataTrigger>
144+
</Style.Triggers>
145+
</Style>
146+
</Path.Style>
147+
</Path>
148+
</Button>
149+
150+
<Border
151+
Grid.Row="1"
152+
Grid.Column="0"
153+
Grid.ColumnSpan="5"
154+
Margin="20 8 20 16"
155+
Background="{DynamicResource Color01B}">
156+
<ListBox
157+
x:Name="PluginListBox"
158+
Padding="0 0 7 0"
159+
Background="{DynamicResource Color01B}"
160+
FontSize="14"
161+
ItemContainerStyle="{DynamicResource PluginList}"
162+
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
163+
SnapsToDevicePixels="True"
164+
Style="{DynamicResource PluginListStyle}"
165+
VirtualizingPanel.ScrollUnit="Pixel"
166+
VirtualizingStackPanel.IsVirtualizing="True"
167+
VirtualizingStackPanel.VirtualizationMode="Recycling">
168+
<ListBox.ItemTemplate>
169+
<DataTemplate>
170+
<cc:InstalledPluginDisplay />
171+
</DataTemplate>
172+
</ListBox.ItemTemplate>
173+
</ListBox>
174+
</Border>
175+
</Grid>
176+
</Border>
177+
</Grid>
178+
</Window>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Windows;
4+
using System.Windows.Input;
5+
using CommunityToolkit.Mvvm.DependencyInjection;
6+
using Flow.Launcher.Core.Plugin;
7+
using Flow.Launcher.Infrastructure.UserSettings;
8+
using Flow.Launcher.ViewModel;
9+
10+
namespace Flow.Launcher;
11+
12+
public partial class PluginSettingsWindow
13+
{
14+
private readonly Settings _settings;
15+
private readonly PluginDisplayModes _displayModes = new();
16+
17+
public PluginSettingsWindow()
18+
{
19+
_settings = Ioc.Default.GetRequiredService<Settings>();
20+
InitializeComponent();
21+
PluginListBox.DataContext = _displayModes;
22+
}
23+
24+
public PluginSettingsWindow(string pluginId)
25+
: this()
26+
{
27+
LoadPlugin(pluginId);
28+
}
29+
30+
private void LoadPlugin(string pluginId)
31+
{
32+
if (string.IsNullOrWhiteSpace(pluginId))
33+
{
34+
App.API.ShowMsgError("Plugin settings", "Invalid plugin id.");
35+
return;
36+
}
37+
38+
var pluginPair = PluginManager.GetPluginForId(pluginId);
39+
if (pluginPair == null)
40+
{
41+
App.API.ShowMsgError("Plugin settings", $"Unable to find plugin: {pluginId}");
42+
return;
43+
}
44+
45+
var pluginSettings = _settings.PluginSettings.GetPluginSettings(pluginId);
46+
if (pluginSettings == null)
47+
{
48+
App.API.ShowMsgError("Plugin settings", $"Unable to load settings for plugin: {pluginPair.Metadata.Name}");
49+
return;
50+
}
51+
52+
var pluginViewModel = new PluginViewModel
53+
{
54+
PluginPair = pluginPair,
55+
PluginSettingsObject = pluginSettings,
56+
IsExpanded = true,
57+
};
58+
59+
PluginListBox.ItemsSource = new List<PluginViewModel> { pluginViewModel };
60+
PluginListBox.SelectedIndex = -1;
61+
Title = $"{pluginPair.Metadata.Name} Settings";
62+
}
63+
64+
private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
65+
{
66+
WindowState = WindowState.Minimized;
67+
}
68+
69+
private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
70+
{
71+
WindowState = WindowState switch
72+
{
73+
WindowState.Maximized => WindowState.Normal,
74+
_ => WindowState.Maximized
75+
};
76+
}
77+
78+
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
79+
{
80+
Close();
81+
}
82+
83+
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
84+
{
85+
Close();
86+
}
87+
88+
private void OnLoaded(object sender, RoutedEventArgs e)
89+
{
90+
RefreshMaximizeRestoreButton();
91+
}
92+
93+
private void Window_StateChanged(object sender, EventArgs e)
94+
{
95+
RefreshMaximizeRestoreButton();
96+
}
97+
98+
private void RefreshMaximizeRestoreButton()
99+
{
100+
if (WindowState == WindowState.Maximized)
101+
{
102+
MaximizeButton.Visibility = Visibility.Hidden;
103+
RestoreButton.Visibility = Visibility.Visible;
104+
}
105+
else
106+
{
107+
MaximizeButton.Visibility = Visibility.Visible;
108+
RestoreButton.Visibility = Visibility.Hidden;
109+
}
110+
}
111+
112+
protected override void OnClosed(EventArgs e)
113+
{
114+
if (!App.LoadingOrExiting)
115+
{
116+
_settings.Save();
117+
App.API.SavePluginSettings();
118+
}
119+
120+
base.OnClosed(e);
121+
}
122+
123+
private sealed class PluginDisplayModes
124+
{
125+
public bool IsOnOffSelected => true;
126+
public bool IsPrioritySelected => false;
127+
public bool IsSearchDelaySelected => false;
128+
public bool IsHomeOnOffSelected => false;
129+
}
130+
}

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ public void OpenSettingDialog()
147147
});
148148
}
149149

150+
public void OpenPluginSettingsWindow(string pluginId)
151+
{
152+
Application.Current.Dispatcher.Invoke(() =>
153+
{
154+
var window = new PluginSettingsWindow(pluginId);
155+
window.Show();
156+
});
157+
}
158+
150159
public void ShellRun(string cmd, string filename = "cmd.exe")
151160
{
152161
var args = filename == "cmd.exe" ? $"/C {cmd}" : $"{cmd}";

Flow.Launcher/ViewModel/MainViewModel.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,7 @@ private void QueryContextMenu()
12751275
{
12761276
List<Result> results = PluginManager.GetContextMenusForPlugin(selected);
12771277
results.Add(ContextMenuTopMost(selected));
1278+
results.Add(ContextMenuPluginSettings(selected));
12781279
results.Add(ContextMenuPluginInfo(selected));
12791280

12801281
if (!string.IsNullOrEmpty(query))
@@ -1827,6 +1828,28 @@ private Result ContextMenuTopMost(Result result)
18271828
return menu;
18281829
}
18291830

1831+
private static Result ContextMenuPluginSettings(Result result)
1832+
{
1833+
var id = result.PluginID;
1834+
1835+
var settings = Localize.iconTraySettings(); // Todo: replace with dedicated translation
1836+
var title = $"{settings}";
1837+
1838+
var menu = new Result
1839+
{
1840+
Title = title,
1841+
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE713"),
1842+
PluginDirectory = Constant.ProgramDirectory,
1843+
Action = _ =>
1844+
{
1845+
App.API.OpenPluginSettingsWindow(id);
1846+
return true;
1847+
},
1848+
OriginQuery = result.OriginQuery
1849+
};
1850+
return menu;
1851+
}
1852+
18301853
private static Result ContextMenuPluginInfo(Result result)
18311854
{
18321855
var id = result.PluginID;

0 commit comments

Comments
 (0)