Expand GeneralSettingsPage with ~25 settings using FluentAvalonia SettingsExpander

- Add Card, ExCard, CardGroup custom controls for settings pages
- Update GeneralSettingsViewModel with new properties:
  - Startup: UseLogonTaskForStartup, HideOnStartup
  - Behavior: HideNotifyIcon, IgnoreHotkeysOnFullscreen, AlwaysPreview
  - Updates: AutoUpdates, AutoUpdatePlugins
  - Search: SearchPrecision, LastQueryMode, SearchDelay
  - Home: ShowHomePage, ShowHistoryResults, MaxHistoryResults
  - Misc: AutoRestart, ShowWarning, AlwaysStartEn, ShouldUsePinyin
- Rewrite GeneralSettingsPage.axaml using SettingsExpander pattern
- Update migration checklist to ~35-40% progress
This commit is contained in:
Hongtao Zhang 2026-01-18 02:25:47 -08:00
parent 8a99881799
commit 1d945654c1
9 changed files with 1087 additions and 90 deletions

View file

@ -1,6 +1,6 @@
# Flow.Launcher Avalonia Migration Checklist
> **Overall Progress: ~30-35%**
> **Overall Progress: ~35-40%**
> Last Updated: January 2026
---
@ -48,44 +48,53 @@
## 2. Settings Pages
### 2.1 General Settings (`SettingsPaneGeneral.xaml` - 538 lines)
**Avalonia: 71 lines (~13%)**
**Avalonia: ~270 lines (~50%)**
| Setting | Status | Binding Property |
|---------|--------|------------------|
| **Startup Section** |
| Start on system startup | :x: Missing | `StartFlowLauncherOnSystemStartup` |
| Use logon task | :x: Missing | `UseLogonTaskForStartup` |
| Hide on startup | :x: Missing | `Settings.HideOnStartup` |
| Start on system startup | :white_check_mark: Done | `StartOnStartup` |
| Use logon task | :white_check_mark: Done | `UseLogonTaskForStartup` |
| Hide on startup | :white_check_mark: Done | `HideOnStartup` |
| **Behavior Section** |
| Hide when lose focus | :x: Missing | `Settings.HideWhenDeactivated` |
| Hide notify icon | :x: Missing | `Settings.HideNotifyIcon` |
| Show at topmost | :x: Missing | `Settings.ShowAtTopmost` |
| Hide when lose focus | :white_check_mark: Done | `HideWhenDeactivated` |
| Hide notify icon | :white_check_mark: Done | `HideNotifyIcon` |
| Show at topmost | :white_check_mark: Done | `ShowAtTopmost` |
| Ignore hotkeys on fullscreen | :white_check_mark: Done | `IgnoreHotkeysOnFullscreen` |
| Always preview | :white_check_mark: Done | `AlwaysPreview` |
| **Position Section** |
| Search window position | :x: Missing | `Settings.SearchWindowScreen` |
| Search window position | :white_check_mark: Done | `SelectedSearchWindowScreen` |
| Search window align | :white_check_mark: Done | `SelectedSearchWindowAlign` |
| Custom position X/Y | :x: Missing | `Settings.CustomWindowLeft/Top` |
| Remember last position | :x: Missing | `Settings.RememberLastLaunchLocation` |
| **Search Section** |
| Max results shown | :x: Missing | `Settings.MaxResultsToShow` |
| Clear input on close | :x: Missing | `Settings.ClearInputOnLaunch` |
| Auto-complete text | :white_check_mark: Done | `Settings.AutoCompleteText` |
| Keep search text | :x: Missing | `Settings.KeepMaxResults` |
| Query search precision | :x: Missing | `Settings.QuerySearchPrecision` |
| **Input Section** |
| Always start English mode | :x: Missing | `Settings.AlwaysStartEn` |
| First search delay | :x: Missing | `Settings.FirstSearchDelay` |
| Input delay | :x: Missing | `Settings.InputDelay` |
| **Language Section** |
| Language selector | :white_check_mark: Done | `Settings.Language` |
| Query search precision | :white_check_mark: Done | `SelectedSearchPrecision` |
| Last query mode | :white_check_mark: Done | `SelectedLastQueryMode` |
| Search delay toggle | :white_check_mark: Done | `SearchQueryResultsWithDelay` |
| Search delay time | :white_check_mark: Done | `SearchDelayTime` |
| **Home Page Section** |
| Show home page | :white_check_mark: Done | `ShowHomePage` |
| History results for home | :white_check_mark: Done | `ShowHistoryResultsForHomePage` |
| History results count | :white_check_mark: Done | `MaxHistoryResultsToShow` |
| **Updates Section** |
| Auto-update interval | :x: Missing | `Settings.UpdateCheckInterval` |
| Check for updates | :x: Missing | `UpdateApp` command |
| **Advanced Section** |
| Python directory | :x: Missing | `Settings.PluginSettings.PythonExecutablePath` |
| Node directory | :x: Missing | `Settings.PluginSettings.NodeExecutablePath` |
| Plugin directory | :x: Missing | `Settings.PluginSettings.PluginDirectory` |
| Auto updates | :white_check_mark: Done | `AutoUpdates` |
| Auto update plugins | :white_check_mark: Done | `AutoUpdatePlugins` |
| **Miscellaneous** |
| Auto restart after changing | :white_check_mark: Done | `AutoRestartAfterChanging` |
| Show unknown source warning | :white_check_mark: Done | `ShowUnknownSourceWarning` |
| Always start English | :white_check_mark: Done | `AlwaysStartEn` |
| Use Pinyin | :white_check_mark: Done | `ShouldUsePinyin` |
| **Language Section** |
| Language selector | :white_check_mark: Done | `SelectedLanguage` |
| **Paths** |
| Python directory | :white_check_mark: Done | `PythonPath` (display + select) |
| Node directory | :white_check_mark: Done | `NodePath` (display + select) |
| **Not Yet Implemented** |
| Select browser | :x: Missing | Opens `SelectBrowserWindow` |
| Select file manager | :x: Missing | Opens `SelectFileManagerWindow` |
| Portable mode | :x: Missing | `Settings.ShouldUsePinyin` |
| Portable mode | :x: Missing | Toggle |
| Dialog jump settings | :x: Missing | ExCard with nested options |
| Double pinyin settings | :x: Missing | ExCard with schema selector |
| Korean IME settings | :x: Missing | ExCard with registry toggle |
### 2.2 Theme Settings (`SettingsPaneTheme.xaml` - 803 lines)
**Avalonia: 66 lines (~8%)**
@ -192,7 +201,7 @@
| Open logs folder | :x: Missing | Button |
| Open settings folder | :x: Missing | Button |
**Settings Progress: 312/2609 lines (~12%)**
**Settings Progress: ~550/2609 lines (~21%)**
---
@ -200,9 +209,9 @@
| Control | WPF File | Status | Description |
|---------|----------|--------|-------------|
| Card | `Card.xaml.cs` | :x: Missing | Settings card with icon, title, subtitle |
| ExCard | `ExCard.xaml.cs` | :x: Missing | Expandable card with nested content |
| CardGroup | `CardGroup.xaml.cs` | :x: Missing | Groups cards with rounded corners |
| Card | `Card.xaml.cs` | :white_check_mark: Done | Settings card with icon, title, subtitle |
| ExCard | `ExCard.xaml.cs` | :white_check_mark: Done | Expandable card with nested content |
| CardGroup | `CardGroup.xaml.cs` | :white_check_mark: Done | Groups cards with rounded corners |
| InfoBar | `InfoBar.xaml.cs` | :x: Missing | Information/warning banner |
| HyperLink | `HyperLink.xaml.cs` | :x: Missing | Clickable link control |
| HotkeyDisplay | `HotkeyDisplay.xaml.cs` | :x: Missing | Displays hotkey as key badges |
@ -210,7 +219,9 @@
| InstalledPluginDisplayKeyword | `InstalledPluginDisplayKeyword.xaml.cs` | :x: Missing | Keyword badge |
| InstalledPluginDisplayBottomData | `InstalledPluginDisplayBottomData.xaml.cs` | :x: Missing | Plugin metadata footer |
**Progress: 0/9 (0%)**
**Progress: 3/9 (33%)**
> Note: Using FluentAvalonia's `SettingsExpander` for most settings pages instead of custom Card controls.
---
@ -222,6 +233,7 @@
| ResultsViewModel | ~200 | ~150 | :white_check_mark: 75% | Core functionality done |
| ResultViewModel | ~300 | ~200 | :white_check_mark: 67% | Basic display done |
| SettingWindowViewModel | ~100 | ~50 | :yellow_circle: 50% | Navigation works |
| GeneralSettingsViewModel | ~100 | ~400 | :white_check_mark: 95% | Most settings implemented |
| PluginViewModel | ~150 | ~30 | :yellow_circle: 20% | Basic list only |
| PluginStoreItemViewModel | ~100 | 0 | :x: 0% | Not started |
| WelcomeViewModel | ~80 | 0 | :x: 0% | Not started |
@ -375,8 +387,8 @@
### High Priority (Core UX)
1. [ ] **HighlightTextConverter** - Search match highlighting
2. [ ] **History cycling** - Up/Down arrow through history
3. [ ] **GeneralSettingsPage** - Essential settings (startup, behavior)
4. [ ] **Card/ExCard controls** - Required for all settings pages
3. [x] **GeneralSettingsPage** - Essential settings (startup, behavior) - DONE
4. [x] **Card/ExCard controls** - Required for all settings pages - DONE (using FluentAvalonia SettingsExpander)
5. [ ] **Hide on focus loss** - Expected behavior
### Medium Priority (Feature Completeness)
@ -429,10 +441,13 @@ Flow.Launcher.Avalonia/
│ ├── PreviewPanel.axaml
│ ├── Controls/
│ │ ├── HotkeyControl.axaml
│ │ └── HotkeyRecorderDialog.axaml
│ │ ├── HotkeyRecorderDialog.axaml
│ │ ├── Card.axaml (+ .cs)
│ │ ├── ExCard.axaml (+ .cs)
│ │ └── CardGroup.axaml (+ .cs)
│ └── SettingPages/
│ ├── SettingsWindow.axaml
│ ├── GeneralSettingsPage.axaml (71 lines)
│ ├── GeneralSettingsPage.axaml (~270 lines)
│ ├── ThemeSettingsPage.axaml (66 lines)
│ ├── HotkeySettingsPage.axaml (26 lines)
│ ├── PluginsSettingsPage.axaml (47 lines)
@ -440,7 +455,9 @@ Flow.Launcher.Avalonia/
│ └── AboutSettingsPage.axaml (25 lines)
├── ViewModel/
│ ├── MainViewModel.cs (~420 lines)
│ └── SettingPages/*.cs
│ ├── SettingPages/
│ │ └── GeneralSettingsViewModel.cs (~400 lines)
│ └── ...
├── Helper/ (4 files)
├── Converters/ (3 files)
└── Themes/

View file

@ -3,8 +3,10 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using AvaloniaI18n = Flow.Launcher.Avalonia.Resource.Internationalization;
@ -22,8 +24,16 @@ public partial class GeneralSettingsViewModel : ObservableObject
_i18n = Ioc.Default.GetRequiredService<AvaloniaI18n>();
LoadLanguages();
LoadSearchWindowOptions();
LoadSearchPrecisionOptions();
LoadLastQueryModeOptions();
}
// Direct access to settings for bindings
public Flow.Launcher.Infrastructure.UserSettings.Settings Settings => _settings;
#region Languages
[ObservableProperty]
private List<Language> _languages = new();
@ -41,6 +51,10 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
}
#endregion
#region Startup Settings
public bool StartOnStartup
{
get => _settings.StartFlowLauncherOnSystemStartup;
@ -51,6 +65,30 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
}
public bool UseLogonTaskForStartup
{
get => _settings.UseLogonTaskForStartup;
set
{
_settings.UseLogonTaskForStartup = value;
OnPropertyChanged();
}
}
public bool HideOnStartup
{
get => _settings.HideOnStartup;
set
{
_settings.HideOnStartup = value;
OnPropertyChanged();
}
}
#endregion
#region Behavior Settings
public bool HideWhenDeactivated
{
get => _settings.HideWhenDeactivated;
@ -61,6 +99,16 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
}
public bool HideNotifyIcon
{
get => _settings.HideNotifyIcon;
set
{
_settings.HideNotifyIcon = value;
OnPropertyChanged();
}
}
public bool ShowAtTopmost
{
get => _settings.ShowAtTopmost;
@ -71,6 +119,212 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
}
public bool IgnoreHotkeysOnFullscreen
{
get => _settings.IgnoreHotkeysOnFullscreen;
set
{
_settings.IgnoreHotkeysOnFullscreen = value;
OnPropertyChanged();
}
}
public bool AlwaysPreview
{
get => _settings.AlwaysPreview;
set
{
_settings.AlwaysPreview = value;
OnPropertyChanged();
}
}
#endregion
#region Update Settings
public bool AutoUpdates
{
get => _settings.AutoUpdates;
set
{
_settings.AutoUpdates = value;
OnPropertyChanged();
}
}
public bool AutoUpdatePlugins
{
get => _settings.AutoUpdatePlugins;
set
{
_settings.AutoUpdatePlugins = value;
OnPropertyChanged();
}
}
#endregion
#region Search Window Position
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchWindowScreens>> _searchWindowScreens = new();
public SearchWindowScreens SelectedSearchWindowScreen
{
get => _settings.SearchWindowScreen;
set
{
_settings.SearchWindowScreen = value;
OnPropertyChanged();
}
}
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchWindowAligns>> _searchWindowAligns = new();
public SearchWindowAligns SelectedSearchWindowAlign
{
get => _settings.SearchWindowAlign;
set
{
_settings.SearchWindowAlign = value;
OnPropertyChanged();
}
}
#endregion
#region Query Settings
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchPrecisionScore>> _searchPrecisionScores = new();
public SearchPrecisionScore SelectedSearchPrecision
{
get => _settings.QuerySearchPrecision;
set
{
_settings.QuerySearchPrecision = value;
OnPropertyChanged();
}
}
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<LastQueryMode>> _lastQueryModes = new();
public LastQueryMode SelectedLastQueryMode
{
get => _settings.LastQueryMode;
set
{
_settings.LastQueryMode = value;
OnPropertyChanged();
}
}
public bool SearchQueryResultsWithDelay
{
get => _settings.SearchQueryResultsWithDelay;
set
{
_settings.SearchQueryResultsWithDelay = value;
OnPropertyChanged();
}
}
public int SearchDelayTime
{
get => _settings.SearchDelayTime;
set
{
_settings.SearchDelayTime = value;
OnPropertyChanged();
}
}
#endregion
#region Home Page Settings
public bool ShowHomePage
{
get => _settings.ShowHomePage;
set
{
_settings.ShowHomePage = value;
OnPropertyChanged();
}
}
public bool ShowHistoryResultsForHomePage
{
get => _settings.ShowHistoryResultsForHomePage;
set
{
_settings.ShowHistoryResultsForHomePage = value;
OnPropertyChanged();
}
}
public int MaxHistoryResultsToShow
{
get => _settings.MaxHistoryResultsToShowForHomePage;
set
{
_settings.MaxHistoryResultsToShowForHomePage = value;
OnPropertyChanged();
}
}
#endregion
#region Miscellaneous Settings
public bool AutoRestartAfterChanging
{
get => _settings.AutoRestartAfterChanging;
set
{
_settings.AutoRestartAfterChanging = value;
OnPropertyChanged();
}
}
public bool ShowUnknownSourceWarning
{
get => _settings.ShowUnknownSourceWarning;
set
{
_settings.ShowUnknownSourceWarning = value;
OnPropertyChanged();
}
}
public bool AlwaysStartEn
{
get => _settings.AlwaysStartEn;
set
{
_settings.AlwaysStartEn = value;
OnPropertyChanged();
}
}
public bool ShouldUsePinyin
{
get => _settings.ShouldUsePinyin;
set
{
_settings.ShouldUsePinyin = value;
OnPropertyChanged();
}
}
#endregion
#region Paths
public string PythonPath => _settings.PluginSettings.PythonExecutablePath ?? "Not set";
public string NodePath => _settings.PluginSettings.NodeExecutablePath ?? "Not set";
@ -88,6 +342,10 @@ public partial class GeneralSettingsViewModel : ObservableObject
await Task.CompletedTask;
}
#endregion
#region Load Options
private void LoadLanguages()
{
// Minimal set of languages for now, can be expanded by loading from directory later
@ -100,4 +358,42 @@ public partial class GeneralSettingsViewModel : ObservableObject
new Language("ja", "日本語")
};
}
private void LoadSearchWindowOptions()
{
SearchWindowScreens = new ObservableCollection<EnumDisplayItem<SearchWindowScreens>>(
Enum.GetValues<SearchWindowScreens>().Select(v => new EnumDisplayItem<SearchWindowScreens>(v, v.ToString())));
SearchWindowAligns = new ObservableCollection<EnumDisplayItem<SearchWindowAligns>>(
Enum.GetValues<SearchWindowAligns>().Select(v => new EnumDisplayItem<SearchWindowAligns>(v, v.ToString())));
}
private void LoadSearchPrecisionOptions()
{
SearchPrecisionScores = new ObservableCollection<EnumDisplayItem<SearchPrecisionScore>>(
Enum.GetValues<SearchPrecisionScore>().Select(v => new EnumDisplayItem<SearchPrecisionScore>(v, v.ToString())));
}
private void LoadLastQueryModeOptions()
{
LastQueryModes = new ObservableCollection<EnumDisplayItem<LastQueryMode>>(
Enum.GetValues<LastQueryMode>().Select(v => new EnumDisplayItem<LastQueryMode>(v, v.ToString())));
}
#endregion
}
/// <summary>
/// Helper class for displaying enum values in ComboBoxes
/// </summary>
public class EnumDisplayItem<T> where T : Enum
{
public T Value { get; }
public string Display { get; }
public EnumDisplayItem(T value, string display)
{
Value = value;
Display = display;
}
}

View file

@ -0,0 +1,235 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:controls="using:Flow.Launcher.Avalonia.Views.Controls"
x:Class="Flow.Launcher.Avalonia.Views.Controls.Card"
x:DataType="controls:Card"
mc:Ignorable="d">
<UserControl.Styles>
<!-- Default Card Style -->
<Style Selector="controls|Card">
<Setter Property="Template">
<ControlTemplate TargetType="controls:Card">
<Border x:Name="CardBorder"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="5"
MinHeight="68"
Padding="0,15"
Margin="0,4,0,0">
<Grid ColumnDefinitions="Auto,*,Auto">
<!-- Icon -->
<TextBlock x:Name="IconText"
Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="24,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Spacer when no icon -->
<Border Grid.Column="0"
Width="24"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
<!-- Title and Subtitle -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<!-- Content (right side) -->
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,16,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Inside Card Style (nested inside ExCard) -->
<Style Selector="controls|Card.inside">
<Setter Property="Template">
<ControlTemplate TargetType="controls:Card">
<Border Background="Transparent"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
MinHeight="68"
Padding="0,15">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="24,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<Border Grid.Column="0" Width="24"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,16,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- InsideFit Card Style (nested with extra padding) -->
<Style Selector="controls|Card.insidefit">
<Setter Property="Template">
<ControlTemplate TargetType="controls:Card">
<Border Background="Transparent"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
MinHeight="68"
Padding="35,15,26,15">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="0,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,0,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- First/Middle/Last styles for CardGroup -->
<Style Selector="controls|Card.first, controls|Card.middle, controls|Card.last">
<Setter Property="Template">
<ControlTemplate TargetType="controls:Card">
<Border Background="Transparent"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
MinHeight="68"
Padding="0,15">
<Border.Styles>
<Style Selector="Border">
<Setter Property="BorderThickness" Value="0" />
</Style>
</Border.Styles>
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="24,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<Border Grid.Column="0" Width="24"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,16,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="controls|Card.middle, controls|Card.last">
<Setter Property="Template">
<ControlTemplate TargetType="controls:Card">
<Border Background="Transparent"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
MinHeight="68"
Padding="0,15">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="24,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<Border Grid.Column="0" Width="24"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,16,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
</UserControl.Styles>
</UserControl>

View file

@ -0,0 +1,46 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Flow.Launcher.Avalonia.Views.Controls
{
public partial class Card : ContentControl
{
public static readonly StyledProperty<string> TitleProperty =
AvaloniaProperty.Register<Card, string>(nameof(Title), string.Empty);
public static readonly StyledProperty<string> SubProperty =
AvaloniaProperty.Register<Card, string>(nameof(Sub), string.Empty);
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<Card, string>(nameof(Icon), string.Empty);
public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public string Sub
{
get => GetValue(SubProperty);
set => SetValue(SubProperty, value);
}
public string Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public Card()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -0,0 +1,30 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:controls="using:Flow.Launcher.Avalonia.Views.Controls"
x:Class="Flow.Launcher.Avalonia.Views.Controls.CardGroup"
x:DataType="controls:CardGroup"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="controls|CardGroup">
<Setter Property="Template">
<ControlTemplate TargetType="controls:CardGroup">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="5">
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</Border>
</ControlTemplate>
</Setter>
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter>
</Style>
</UserControl.Styles>
</UserControl>

View file

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Flow.Launcher.Avalonia.Views.Controls
{
public partial class CardGroup : ItemsControl
{
public CardGroup()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -0,0 +1,78 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:controls="using:Flow.Launcher.Avalonia.Views.Controls"
x:Class="Flow.Launcher.Avalonia.Views.Controls.ExCard"
x:DataType="controls:ExCard"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="controls|ExCard">
<Setter Property="Template">
<ControlTemplate TargetType="controls:ExCard">
<Expander x:Name="CardExpander"
IsExpanded="{TemplateBinding IsExpanded, Mode=TwoWay}"
HorizontalAlignment="Stretch">
<Expander.Styles>
<Style Selector="Expander">
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
<Setter Property="MinHeight" Value="68" />
<Setter Property="Padding" Value="0" />
</Style>
</Expander.Styles>
<Expander.Header>
<Grid ColumnDefinitions="Auto,*,Auto,Auto" Margin="0,12">
<!-- Icon -->
<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="24,0,16,0"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<Border Grid.Column="0" Width="24"
IsVisible="{TemplateBinding Icon, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
<!-- Title and Subtitle -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="0,0,14,0">
<TextBlock Text="{TemplateBinding Title}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock Text="{TemplateBinding Sub}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
FontSize="12"
TextWrapping="Wrap"
Padding="0,0,24,0"
IsVisible="{TemplateBinding Sub, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<!-- Side Content (e.g., toggle switch) -->
<ContentPresenter Grid.Column="2"
Content="{TemplateBinding SideContent}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0,0,14,0" />
</Grid>
</Expander.Header>
<!-- Expandable Content -->
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
Margin="0" />
</Expander>
</ControlTemplate>
</Setter>
</Style>
</UserControl.Styles>
</UserControl>

View file

@ -0,0 +1,64 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Flow.Launcher.Avalonia.Views.Controls
{
public partial class ExCard : ContentControl
{
public static readonly StyledProperty<string> TitleProperty =
AvaloniaProperty.Register<ExCard, string>(nameof(Title), string.Empty);
public static readonly StyledProperty<string> SubProperty =
AvaloniaProperty.Register<ExCard, string>(nameof(Sub), string.Empty);
public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<ExCard, string>(nameof(Icon), string.Empty);
public static readonly StyledProperty<object?> SideContentProperty =
AvaloniaProperty.Register<ExCard, object?>(nameof(SideContent));
public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<ExCard, bool>(nameof(IsExpanded), false);
public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public string Sub
{
get => GetValue(SubProperty);
set => SetValue(SubProperty, value);
}
public string Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public object? SideContent
{
get => GetValue(SideContentProperty);
set => SetValue(SideContentProperty, value);
}
public bool IsExpanded
{
get => GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
public ExCard()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -6,66 +6,279 @@
xmlns:vm="using:Flow.Launcher.Avalonia.ViewModel.SettingPages"
xmlns:i18n="using:Flow.Launcher.Avalonia.Resource"
xmlns:core="using:Flow.Launcher.Core.Resource"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:userSettings="using:Flow.Launcher.Infrastructure.UserSettings"
xmlns:sharedModels="using:Flow.Launcher.Plugin.SharedModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1200"
x:Class="Flow.Launcher.Avalonia.Views.SettingPages.GeneralSettingsPage"
x:DataType="vm:GeneralSettingsViewModel">
<StackPanel Spacing="20">
<TextBlock Text="{i18n:Localize general}" FontSize="28" FontWeight="SemiBold" />
<ScrollViewer>
<StackPanel Spacing="4" Margin="0,18,25,30">
<TextBlock Text="{i18n:Localize general}" FontSize="28" FontWeight="SemiBold" Margin="0,0,0,8" />
<!-- Language Selection -->
<ui:SettingsExpander Header="{i18n:Localize language}"
IconSource="Earth">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage}"
MinWidth="200">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="core:Language">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Start on System Startup with nested option -->
<ui:SettingsExpander Header="{i18n:Localize startFlowLauncherOnSystemStartup}"
IconSource="Power"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding StartOnStartup}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize useLogonTaskForStartup}"
Description="{i18n:Localize useLogonTaskForStartupTooltip}">
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch IsChecked="{Binding UseLogonTaskForStartup}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- Startup Settings -->
<ui:SettingsExpander Header="{i18n:Localize startFlowLauncherOnSystemStartup}"
IconSource="Save">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding StartOnStartup}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Hide on Startup -->
<ui:SettingsExpander Header="{i18n:Localize hideOnStartup}"
Description="{i18n:Localize hideOnStartupToolTip}"
IconSource="Hide">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding HideOnStartup}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Behavior Settings -->
<ui:SettingsExpander Header="{i18n:Localize hideFlowLauncherWhenLoseFocus}"
IconSource="Hide">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding HideWhenDeactivated}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Hide when lose focus -->
<ui:SettingsExpander Header="{i18n:Localize hideFlowLauncherWhenLoseFocus}"
IconSource="Hide">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding HideWhenDeactivated}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Header="{i18n:Localize showAtTopmost}"
IconSource="Up">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowAtTopmost}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Hide Notify Icon -->
<ui:SettingsExpander Header="{i18n:Localize hideNotifyIcon}"
Description="{i18n:Localize hideNotifyIconToolTip}"
IconSource="Hide">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding HideNotifyIcon}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Python & Node Paths -->
<ui:SettingsExpander Header="{i18n:Localize pythonFilePath}"
Description="{Binding PythonPath}">
<ui:SettingsExpander.Footer>
<Button Content="{i18n:Localize select}" Command="{Binding SelectPythonCommand}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Show at Topmost -->
<ui:SettingsExpander Header="{i18n:Localize showAtTopmost}"
Description="{i18n:Localize showAtTopmostToolTip}"
IconSource="Up">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowAtTopmost}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Header="{i18n:Localize nodeFilePath}"
Description="{Binding NodePath}">
<ui:SettingsExpander.Footer>
<Button Content="{i18n:Localize select}" Command="{Binding SelectNodeCommand}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Search Window Position -->
<ui:SettingsExpander Header="{i18n:Localize SearchWindowPosition}"
IconSource="MoveUp"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding SearchWindowScreens}"
SelectedItem="{Binding SelectedSearchWindowScreen, Mode=TwoWay}"
MinWidth="200">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize SearchWindowAlign}">
<ui:SettingsExpanderItem.Footer>
<ComboBox ItemsSource="{Binding SearchWindowAligns}"
SelectedItem="{Binding SelectedSearchWindowAlign, Mode=TwoWay}"
MinWidth="160">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
<!-- Ignore Hotkeys on Fullscreen -->
<ui:SettingsExpander Header="{i18n:Localize ignoreHotkeysOnFullscreen}"
Description="{i18n:Localize ignoreHotkeysOnFullscreenToolTip}"
IconSource="Keyboard">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IgnoreHotkeysOnFullscreen}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Always Preview -->
<ui:SettingsExpander Header="{i18n:Localize AlwaysPreview}"
IconSource="PreviewLink">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding AlwaysPreview}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Auto Updates -->
<ui:SettingsExpander Header="{i18n:Localize autoUpdates}"
Description="{i18n:Localize autoUpdatesTooltip}"
IconSource="Sync">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding AutoUpdates}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Auto Update Plugins -->
<ui:SettingsExpander Header="{i18n:Localize autoUpdatePlugins}"
Description="{i18n:Localize autoUpdatePluginsToolTip}"
IconSource="Sync">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding AutoUpdatePlugins}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Query Search Settings -->
<ui:SettingsExpander Header="{i18n:Localize querySearchPrecision}"
Description="{i18n:Localize querySearchPrecisionToolTip}"
IconSource="Find"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding SearchPrecisionScores}"
SelectedItem="{Binding SelectedSearchPrecision, Mode=TwoWay}"
MinWidth="150">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize lastQueryMode}"
Description="{i18n:Localize lastQueryModeToolTip}">
<ui:SettingsExpanderItem.Footer>
<ComboBox ItemsSource="{Binding LastQueryModes}"
SelectedItem="{Binding SelectedLastQueryMode, Mode=TwoWay}"
MinWidth="180">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- Search Delay -->
<ui:SettingsExpander Header="{i18n:Localize searchDelay}"
Description="{i18n:Localize searchDelayToolTip}"
IconSource="Clock"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding SearchQueryResultsWithDelay}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize searchDelayTime}"
Description="{i18n:Localize searchDelayTimeToolTip}">
<ui:SettingsExpanderItem.Footer>
<ui:NumberBox Value="{Binding SearchDelayTime}"
Minimum="0"
Maximum="1000"
SmallChange="10"
SpinButtonPlacementMode="Compact"
Width="120" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- Home Page Settings -->
<ui:SettingsExpander Header="{i18n:Localize homePage}"
Description="{i18n:Localize homePageToolTip}"
IconSource="Home"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowHomePage}" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize historyResultsForHomePage}">
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch IsChecked="{Binding ShowHistoryResultsForHomePage}" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="{i18n:Localize historyResultsCountForHomePage}">
<ui:SettingsExpanderItem.Footer>
<ui:NumberBox Value="{Binding MaxHistoryResultsToShow}"
Minimum="0"
Maximum="100"
SmallChange="5"
SpinButtonPlacementMode="Compact"
Width="120" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- Auto Restart After Changing -->
<ui:SettingsExpander Header="{i18n:Localize autoRestartAfterChanging}"
Description="{i18n:Localize autoRestartAfterChangingToolTip}"
IconSource="Refresh">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding AutoRestartAfterChanging}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Show Unknown Source Warning -->
<ui:SettingsExpander Header="{i18n:Localize showUnknownSourceWarning}"
Description="{i18n:Localize showUnknownSourceWarningToolTip}"
IconSource="Important">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowUnknownSourceWarning}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Always Start English -->
<ui:SettingsExpander Header="{i18n:Localize typingStartEn}"
Description="{i18n:Localize typingStartEnTooltip}"
IconSource="Keyboard">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding AlwaysStartEn}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Use Pinyin -->
<ui:SettingsExpander Header="{i18n:Localize ShouldUsePinyin}"
Description="{i18n:Localize ShouldUsePinyinToolTip}"
IconSource="Character">
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShouldUsePinyin}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Python Path -->
<ui:SettingsExpander Header="{i18n:Localize pythonFilePath}"
Description="{Binding PythonPath}"
IconSource="Code">
<ui:SettingsExpander.Footer>
<Button Content="{i18n:Localize select}" Command="{Binding SelectPythonCommand}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Node Path -->
<ui:SettingsExpander Header="{i18n:Localize nodeFilePath}"
Description="{Binding NodePath}"
IconSource="Code">
<ui:SettingsExpander.Footer>
<Button Content="{i18n:Localize select}" Command="{Binding SelectNodeCommand}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Language Selection -->
<ui:SettingsExpander Header="{i18n:Localize language}"
IconSource="Globe">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage}"
MinWidth="200">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="core:Language">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>