The rest of the settings window cleanup

This commit is contained in:
Yusyuriv 2024-05-20 09:05:22 +06:00
parent baeb01f758
commit c350c5d7cf
No known key found for this signature in database
GPG key ID: A91C52E6F73148E0
5 changed files with 235 additions and 1386 deletions

View file

@ -54,7 +54,7 @@
Margin="0 0 22 0"
VerticalAlignment="Center"
Command="{Binding EditPluginPriorityCommand}"
Content="{Binding Priority, UpdateSourceTrigger=PropertyChanged}"
Content="{Binding Priority}"
Cursor="Hand"
ToolTip="{DynamicResource priorityToolTip}">
<!--#region Priority Button Style-->

View file

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
@ -11,7 +10,7 @@ using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPanePluginsViewModel : BaseModel
public class SettingsPanePluginsViewModel : BaseModel
{
private readonly Settings _settings;
@ -23,12 +22,18 @@ public partial class SettingsPanePluginsViewModel : BaseModel
public string FilterText { get; set; } = string.Empty;
public PluginViewModel? SelectedPlugin { get; set; }
private IEnumerable<PluginViewModel>? _pluginViewModels;
private IEnumerable<PluginViewModel> PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel { PluginPair = p })
.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) ||
@ -36,13 +41,4 @@ public partial class SettingsPanePluginsViewModel : BaseModel
StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
)
.ToList();
[RelayCommand]
private void TogglePlugin()
{
if (SelectedPlugin is null) return;
var id = 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 = SelectedPlugin.PluginPair.Metadata.Disabled;
}
}

View file

@ -1,541 +1,172 @@
using Flow.Launcher.Core.Plugin;
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;
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 Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
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 TextBox = System.Windows.Controls.TextBox;
using ThemeManager = ModernWpf.ThemeManager;
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;
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;
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
{
settings = viewModel.Settings;
DataContext = viewModel;
this.viewModel = viewModel;
API = api;
InitializePosition();
InitializeComponent();
NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
}
#region General
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;
//pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource);
//pluginListView.Filter = PluginListFilter;
//pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
//pluginStoreView.Filter = PluginStoreFilter;
//viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged);
InitializePosition();
}
//private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e)
//{
// 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);
//}
/** For Navigation View **/
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.IsSettingsSelected)
{
ContentFrame.Navigate(typeof(SettingsPaneGeneral));
}
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, new PaneData(settings, viewModel.Updater, viewModel.Portable));
}
}
public record PaneData(Settings Settings, Updater Updater, IPortable Portable);
_settings = viewModel.Settings;
DataContext = viewModel;
_viewModel = viewModel;
_api = api;
InitializePosition();
InitializeComponent();
NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
}
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)
{
return;
}
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
textBox.MoveFocus(tRequest);
}
/* Custom TitleBar */
private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
{
WindowState = WindowState switch
{
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();
}
private void InitializePosition()
{
if (_settings.SettingWindowTop >= 0 && _settings.SettingWindowLeft >= 0)
{
Top = _settings.SettingWindowTop;
Left = _settings.SettingWindowLeft;
}
else
{
Top = WindowTop();
Left = WindowLeft();
}
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

@ -62,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
{
@ -96,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)
{
@ -106,6 +111,7 @@ namespace Flow.Launcher.ViewModel
public void ChangePriority(int newPriority)
{
PluginPair.Metadata.Priority = newPriority;
PluginSettingsObject.Priority = newPriority;
OnPropertyChanged(nameof(Priority));
}

View file

@ -1,850 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Core;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Input;
using System.Globalization;
using System.Runtime.CompilerServices;
using Flow.Launcher.Infrastructure.Hotkey;
using ModernWpf.Media.Animation;
namespace Flow.Launcher.ViewModel
namespace Flow.Launcher.ViewModel;
public class SettingWindowViewModel : BaseModel
{
public partial class SettingWindowViewModel : BaseModel
private readonly FlowLauncherJsonStorage<Settings> _storage;
public Updater Updater { get; }
public IPortable Portable { get; }
public Settings Settings { get; }
public SettingWindowViewModel(Updater updater, IPortable portable)
{
private readonly Updater _updater;
private readonly IPortable _portable;
private readonly FlowLauncherJsonStorage<Settings> _storage;
public Updater Updater => _updater;
public IPortable Portable => _portable;
/* For Navigation View */
private NavigationTransitionInfo _transitionInfo = new SlideNavigationTransitionInfo()
{
Effect = SlideNavigationTransitionEffect.FromBottom
};
public SettingWindowViewModel(Updater updater, IPortable portable)
{
_updater = updater;
_portable = portable;
_storage = new FlowLauncherJsonStorage<Settings>();
Settings = _storage.Load();
Settings.PropertyChanged += (s, e) =>
{
switch (e.PropertyName)
{
case nameof(Settings.ActivateTimes):
OnPropertyChanged(nameof(ActivatedTimes));
break;
case nameof(Settings.WindowSize):
OnPropertyChanged(nameof(WindowWidthSize));
break;
case nameof(Settings.UseDate):
case nameof(Settings.DateFormat):
OnPropertyChanged(nameof(DateText));
break;
case nameof(Settings.UseClock):
case nameof(Settings.TimeFormat):
OnPropertyChanged(nameof(ClockText));
break;
case nameof(Settings.Language):
OnPropertyChanged(nameof(ClockText));
OnPropertyChanged(nameof(DateText));
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
break;
case nameof(Settings.PreviewHotkey):
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
break;
case nameof(Settings.SoundVolume):
OnPropertyChanged(nameof(SoundEffectVolume));
break;
}
};
}
[RelayCommand]
public void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
}
public Settings Settings { get; set; }
public async void UpdateApp()
{
await _updater.UpdateAppAsync(App.API, false);
}
public bool AutoUpdates
{
get => Settings.AutoUpdates;
set
{
Settings.AutoUpdates = value;
if (value)
{
UpdateApp();
}
}
}
public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture;
private Internationalization _translater => InternationalizationManager.Instance;
public string AlwaysPreviewToolTip =>
string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey);
/// <summary>
/// Save Flow settings. Plugins settings are not included.
/// </summary>
public void Save()
{
foreach (var vm in PluginViewModels)
{
var id = vm.PluginPair.Metadata.ID;
Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled;
Settings.PluginSettings.Plugins[id].Priority = vm.Priority;
}
_storage.Save();
}
public string GetFileFromDialog(string title, string filter = "")
{
var dlg = new System.Windows.Forms.OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Multiselect = false,
CheckFileExists = true,
CheckPathExists = true,
Title = title,
Filter = filter
};
var result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
return dlg.FileName;
}
else
{
return string.Empty;
}
}
public string TestProxy()
{
var proxyServer = Settings.Proxy.Server;
var proxyUserName = Settings.Proxy.UserName;
if (string.IsNullOrEmpty(proxyServer))
{
return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
}
if (Settings.Proxy.Port <= 0)
{
return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
{
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port);
}
else
{
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port)
{
Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
};
}
try
{
var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect");
}
else
{
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
}
}
catch
{
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
}
}
#region plugin
public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest";
public PluginViewModel SelectedPlugin { get; set; }
public IList<PluginViewModel> PluginViewModels
{
get => PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel { PluginPair = p })
.ToList();
}
public IList<PluginStoreItemViewModel> ExternalPlugins
{
get
{
return LabelMaker(PluginsManifest.UserPlugins);
}
}
private IList<PluginStoreItemViewModel> LabelMaker(IList<UserPlugin> list)
{
return list.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();
}
public Control SettingProvider
{
get
{
var settingProvider = SelectedPlugin.PluginPair.Plugin as ISettingProvider;
if (settingProvider != null)
{
var control = settingProvider.CreateSettingPanel();
control.HorizontalAlignment = HorizontalAlignment.Stretch;
control.VerticalAlignment = VerticalAlignment.Stretch;
return control;
}
else
{
return new Control();
}
}
}
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
await PluginsManifest.UpdateManifestAsync();
OnPropertyChanged(nameof(ExternalPlugins));
}
internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0)
{
var actionKeyword = plugin.Metadata.ActionKeywords.Count == 0
? string.Empty
: plugin.Metadata.ActionKeywords[actionKeywordPosition];
App.API.ChangeQuery($"{actionKeyword} {queryToDisplay}");
App.API.ShowMainWindow();
}
#endregion
#region theme
public static string Theme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme";
public static string ThemeGallery => @"https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
public string SelectedTheme
{
get { return Settings.Theme; }
set
{
ThemeManager.Instance.ChangeTheme(value);
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
DropShadowEffect = false;
}
}
public List<string> Themes
=> ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList();
public bool DropShadowEffect
{
get { return 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 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 = _translater.GetTranslation(key);
var m = new ColorScheme { Display = display, Value = e, };
modes.Add(m);
}
return modes;
}
}
public class SearchWindowAlign
{
public string Display { get; set; }
public SearchWindowAligns Value { get; set; }
}
public List<SearchWindowAlign> SearchWindowAligns
{
get
{
List<SearchWindowAlign> modes = new List<SearchWindowAlign>();
var enums = (SearchWindowAligns[])Enum.GetValues(typeof(SearchWindowAligns));
foreach (var e in enums)
{
var key = $"SearchWindowAlign{e}";
var display = _translater.GetTranslation(key);
var m = new SearchWindowAlign { Display = display, Value = e, };
modes.Add(m);
}
return modes;
}
}
public List<int> ScreenNumbers
{
get
{
var screens = System.Windows.Forms.Screen.AllScreens;
var screenNumbers = new List<int>();
for (int i = 1; i <= screens.Length; i++)
{
screenNumbers.Add(i);
}
return screenNumbers;
}
}
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 = _translater.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 double SettingWindowWidth
{
get => Settings.SettingWindowWidth;
set => Settings.SettingWindowWidth = value;
}
public double SettingWindowHeight
{
get => Settings.SettingWindowHeight;
set => Settings.SettingWindowHeight = value;
}
public double SettingWindowTop
{
get => Settings.SettingWindowTop;
set => Settings.SettingWindowTop = value;
}
public double SettingWindowLeft
{
get => Settings.SettingWindowLeft;
set => Settings.SettingWindowLeft = value;
}
public Brush PreviewBackground
{
get
{
var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
if (wallpaper != 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();
var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
return brush;
}
else
{
var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
var brush = new SolidColorBrush(wallpaperColor);
return brush;
}
}
}
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
{
if (Fonts.SystemFontFamilies.Count(o =>
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
{
var font = new FontFamily(Settings.QueryBoxFont);
return font;
}
else
{
var font = new FontFamily("Segoe UI");
return font;
}
}
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
{
if (Fonts.SystemFontFamilies.Count(o =>
o.FamilyNames.Values != null &&
o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
{
var font = new FontFamily(Settings.ResultFont);
return font;
}
else
{
var font = new FontFamily("Segoe UI");
return font;
}
}
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;
#endregion
#region hotkey
public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; }
#endregion
#region shortcut
public ObservableCollection<CustomShortcutModel> CustomShortcuts => Settings.CustomShortcuts;
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts => Settings.BuiltinShortcuts;
public CustomShortcutModel? SelectedCustomShortcut { get; set; }
public void DeleteSelectedCustomShortcut()
{
var item = SelectedCustomShortcut;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return;
}
string deleteWarning = string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"),
item.Key, item.Value);
if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Settings.CustomShortcuts.Remove(item);
}
}
public bool EditSelectedCustomShortcut()
{
var item = SelectedCustomShortcut;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return false;
}
var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this);
if (shortcutSettingWindow.ShowDialog() == true)
{
// Fix un-selectable shortcut item after the first selection
// https://stackoverflow.com/questions/16789360/wpf-listbox-items-with-changing-hashcode
SelectedCustomShortcut = null;
item.Key = shortcutSettingWindow.Key;
item.Value = shortcutSettingWindow.Value;
SelectedCustomShortcut = item;
return true;
}
return false;
}
public void AddCustomShortcut()
{
var shortcutSettingWindow = new CustomShortcutSetting(this);
if (shortcutSettingWindow.ShowDialog() == true)
{
var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value);
Settings.CustomShortcuts.Add(shortcut);
}
}
public bool ShortcutExists(string key)
{
return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key);
}
#endregion
#region about
public string Website => Constant.Website;
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
{
get
{
if (Constant.Version == "1.0.0")
{
return Constant.Dev;
}
else
{
return Constant.Version;
}
}
}
public string ActivatedTimes =>
string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
public string CheckLogFolder
{
get
{
var logFiles = GetLogFiles();
long size = logFiles.Sum(file => file.Length);
return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"),
BytesToReadableString(size));
}
}
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();
}
internal 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(CheckLogFolder));
}
internal void OpenLogFolder()
{
App.API.OpenDirectory(GetLogDir(Constant.Version).FullName);
}
internal static string BytesToReadableString(long bytes)
{
const int scale = 1024;
string[] orders = new string[] { "GB", "MB", "KB", "B" };
long max = (long)Math.Pow(scale, orders.Length - 1);
foreach (string order in orders)
{
if (bytes > max)
return string.Format("{0:##.##} {1}", decimal.Divide(bytes, max), order);
max /= scale;
}
return "0 B";
}
#endregion
_storage = new FlowLauncherJsonStorage<Settings>();
Updater = updater;
Portable = portable;
Settings = _storage.Load();
}
public async void UpdateApp()
{
await Updater.UpdateAppAsync(App.API, false);
}
/// <summary>
/// Save Flow settings. Plugins settings are not included.
/// </summary>
public void Save()
{
_storage.Save();
}
public double SettingWindowWidth
{
get => Settings.SettingWindowWidth;
set => Settings.SettingWindowWidth = value;
}
public double SettingWindowHeight
{
get => Settings.SettingWindowHeight;
set => Settings.SettingWindowHeight = value;
}
public double SettingWindowTop
{
get => Settings.SettingWindowTop;
set => Settings.SettingWindowTop = value;
}
public double SettingWindowLeft
{
get => Settings.SettingWindowLeft;
set => Settings.SettingWindowLeft = value;
}
}