refactor: move history storage to infrastructure and implement WPF settings window host in Avalonia

This commit is contained in:
Hongtao Zhang 2026-01-18 23:28:56 -08:00
parent 0e1af2279b
commit fb9721c7f2
13 changed files with 10507 additions and 95 deletions

430
AGENTS.md Normal file
View file

@ -0,0 +1,430 @@
# AGENTS.md - Flow.Launcher
This document provides essential information for AI agents working on the Flow.Launcher codebase.
## Project Overview
Flow.Launcher is a Windows productivity launcher (similar to Alfred/Raycast) built with:
- **WPF** (original UI framework) - `Flow.Launcher/`
- **Avalonia** (migration in progress ~35-40%) - `Flow.Launcher.Avalonia/`
- **.NET 9.0** targeting `net9.0-windows10.0.19041.0`
- **CommunityToolkit.Mvvm** for MVVM patterns
- **FluentAvalonia** for modern UI in Avalonia version
The codebase is actively being migrated from WPF to Avalonia. See `AVALONIA_MIGRATION_CHECKLIST.md` for detailed progress.
---
## Essential Commands
### Build
```bash
# Build entire solution
dotnet build
# Build specific project
dotnet build Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
# Release build
dotnet build -c Release
# Restore dependencies (required for CI)
nuget restore
```
### Test
```bash
# Run all tests
dotnet test
# Run tests with verbosity
dotnet test --verbosity normal
# Run specific test file/class
dotnet test --filter "ClassName=FuzzyMatcherTest"
```
### Run
```bash
# Run WPF version
./Output/Debug/Flow.Launcher.exe
# Run Avalonia version
./Output/Debug/Avalonia/Flow.Launcher.Avalonia.exe
```
### Output Locations
| Configuration | WPF Output | Avalonia Output |
|---------------|------------|-----------------|
| Debug | `Output/Debug/` | `Output/Debug/Avalonia/` |
| Release | `Output/Release/` | `Output/Release/Avalonia/` |
---
## Project Structure
```
Flow.Launcher/
├── Flow.Launcher/ # WPF main application
│ ├── MainWindow.xaml # Main search window
│ ├── SettingWindow.xaml # Settings window
│ ├── ViewModel/ # ViewModels (MVVM)
│ ├── SettingPages/ # Settings page views/viewmodels
│ ├── Helper/ # Utility classes
│ ├── Converters/ # XAML value converters
│ ├── Themes/ # Theme XAML files
│ ├── Languages/ # Localization (*.xaml)
│ └── Resources/ # Icons, fonts, styles
├── Flow.Launcher.Avalonia/ # Avalonia main application (migration)
│ ├── MainWindow.axaml # Main search window
│ ├── Views/ # Avalonia views
│ │ ├── SettingPages/ # Settings pages
│ │ └── Controls/ # Custom controls
│ ├── ViewModel/ # ViewModels
│ ├── Helper/ # Utilities
│ ├── Converters/ # Avalonia converters
│ └── Themes/ # Avalonia themes
├── Flow.Launcher.Plugin/ # Plugin SDK (shared)
│ ├── Interfaces/ # IPlugin, IPublicAPI, etc.
│ ├── Result.cs # Search result model
│ ├── Query.cs # Query model
│ └── PluginMetadata.cs # Plugin metadata
├── Flow.Launcher.Infrastructure/ # Shared infrastructure
│ ├── UserSettings/ # Settings models
│ ├── StringMatcher.cs # Fuzzy search algorithm
│ └── Logger/ # Logging utilities
├── Flow.Launcher.Core/ # Core business logic
│ ├── Plugin/ # Plugin management
│ │ └── PluginManager.cs # Plugin lifecycle
│ ├── Resource/ # Internationalization
│ └── ExternalPlugins/ # Plugin store
├── Plugins/ # Built-in plugins
│ ├── Flow.Launcher.Plugin.Calculator/
│ ├── Flow.Launcher.Plugin.Explorer/
│ ├── Flow.Launcher.Plugin.Program/
│ ├── Flow.Launcher.Plugin.WebSearch/
│ └── ... (10+ plugins)
├── Flow.Launcher.Test/ # Unit tests (NUnit)
└── Scripts/ # Build scripts
└── post_build.ps1 # Packaging script
```
---
## Code Conventions
### Naming
- **PascalCase** for public members, types, properties, methods
- **camelCase** for local variables, parameters
- **_camelCase** for private fields (underscore prefix)
- **UPPER_CASE** constants are PascalCase per `.editorconfig`
- No `this.` qualifier (per `.editorconfig`)
### C# Style
```csharp
// File-scoped namespaces preferred
namespace Flow.Launcher.ViewModel;
// Prefer var when type is apparent
var results = new List<Result>();
// Braces always required
if (condition)
{
DoSomething();
}
// Allman brace style (new line before opening brace)
public void Method()
{
// ...
}
// 4-space indentation for code
// 2-space indentation for XML/XAML
```
### MVVM Patterns
The project uses **CommunityToolkit.Mvvm** with source generators:
```csharp
// ViewModels use ObservableObject base
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] generates property + change notification
[ObservableProperty]
private string _queryText = string.Empty;
// [RelayCommand] generates ICommand implementation
[RelayCommand]
private void Search() { ... }
}
// WPF uses BaseModel which wraps INotifyPropertyChanged
public class MainViewModel : BaseModel
{
public string QueryText
{
get => _queryText;
set
{
if (_queryText != value)
{
_queryText = value;
OnPropertyChanged();
}
}
}
}
```
### XAML Style
Uses XamlStyler with specific rules (see `Settings.XamlStyler`):
- One attribute per line (except when ≤2)
- Specific attribute ordering (x:Class first, then xmlns, etc.)
- Space before closing slash: `<Element />`
**WPF XAML** (`.xaml`):
```xml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
```
**Avalonia AXAML** (`.axaml`):
```xml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls">
```
---
## Plugin Architecture
### Plugin Interface
All plugins implement `IPlugin` or `IAsyncPlugin`:
```csharp
public interface IPlugin : IAsyncPlugin
{
List<Result> Query(Query query);
void Init(PluginInitContext context);
}
public interface IAsyncPlugin
{
Task<List<Result>> QueryAsync(Query query, CancellationToken token);
Task InitAsync(PluginInitContext context);
}
```
### Creating Results
```csharp
return new List<Result>
{
new Result
{
Title = "Result Title",
SubTitle = "Optional subtitle",
IcoPath = "Images/icon.png", // Relative to plugin directory
Score = 100, // Higher = better match
Action = context =>
{
// Execute action, return true to hide window
return true;
}
}
};
```
### Plugin Metadata
Each plugin needs `plugin.json`:
```json
{
"ID": "unique-guid",
"ActionKeyword": "keyword",
"Name": "Plugin Name",
"Description": "Description",
"Author": "Author",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://...",
"IcoPath": "Images\\icon.png",
"ExecuteFileName": "Plugin.dll"
}
```
### Plugin Settings
```csharp
// Load settings
var settings = context.API.LoadSettingJsonStorage<MySettings>();
// Save (automatic on app close, or manual)
context.API.SaveSettingJsonStorage<MySettings>();
```
---
## Key Classes & Files
| Class/File | Purpose |
|------------|---------|
| `MainViewModel.cs` | Main search window logic |
| `ResultsViewModel.cs` | Search results management |
| `Settings.cs` | User settings model |
| `PluginManager.cs` | Plugin lifecycle management |
| `StringMatcher.cs` | Fuzzy search algorithm |
| `IPublicAPI.cs` | Plugin API interface |
| `Result.cs` | Search result model |
| `Query.cs` | Search query model |
---
## Testing
- **Framework**: NUnit 4.x
- **Mocking**: Moq
- **Test file naming**: `*Test.cs`
- **Test class attribute**: `[TestFixture]`
```csharp
[TestFixture]
public class FuzzyMatcherTest
{
[Test]
public void WhenSearching_ThenReturnsExpectedResults()
{
var matcher = new StringMatcher(null);
var result = matcher.FuzzyMatch("chr", "Chrome");
ClassicAssert.IsTrue(result.RawScore > 0);
}
[TestCase("chrome")]
[TestCase("chr")]
public void ParameterizedTest(string query)
{
// ...
}
}
```
---
## Avalonia Migration Notes
When working on the Avalonia migration:
1. **File extensions**: Use `.axaml` not `.xaml` for Avalonia
2. **Namespace**: Use `xmlns="https://github.com/avaloniaui"`
3. **Controls**: Use FluentAvalonia `ui:SettingsExpander` for settings pages
4. **Visibility**: Use `IsVisible` not `Visibility` (no `Collapsed` enum)
5. **Converters**: Different converter approach (see `Converters/`)
6. **Define directive**: `#if AVALONIA` for conditional compilation
**Key differences**:
```xml
<!-- WPF -->
<Button Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibility}}" />
<!-- Avalonia -->
<Button IsVisible="{Binding IsVisible}" />
```
---
## Localization
Resources are in `Languages/*.xaml` files:
```xml
<!-- Languages/en.xaml -->
<sys:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</sys:String>
```
Usage in XAML:
```xml
<!-- WPF -->
<TextBlock Text="{DynamicResource startFlowLauncherOnSystemStartup}" />
<!-- Avalonia (custom extension) -->
<TextBlock Text="{i18n:Localize startFlowLauncherOnSystemStartup}" />
```
---
## Common Tasks
### Adding a new setting
1. Add property to `Flow.Launcher.Infrastructure/UserSettings/Settings.cs`
2. Add UI in appropriate settings page (WPF and/or Avalonia)
3. Bind to ViewModel property
### Adding a new plugin
1. Create project under `Plugins/` folder
2. Reference `Flow.Launcher.Plugin`
3. Implement `IPlugin` or `IAsyncPlugin`
4. Add `plugin.json` metadata
5. Add to solution and build dependencies in WPF project
### Fixing a ViewModel binding
1. Ensure property raises `PropertyChanged` or uses `[ObservableProperty]`
2. Check DataContext is set correctly
3. Verify binding path matches property name exactly
---
## Gotchas & Tips
1. **Build order matters**: WPF project depends on plugins being built first
2. **Kill running instance**: Build kills running `Flow.Launcher.exe` automatically
3. **Plugin isolation**: Plugins run in separate app domains, can't share state directly
4. **Settings persist**: Changes to `Settings.cs` properties auto-save via Fody PropertyChanged
5. **Windows Search**: Some tests require Windows Search service (`WSearch`) to be running
6. **Nullable**: Avalonia project has `<Nullable>enable</Nullable>`, WPF does not
7. **Framework reference**: Avalonia still references WPF assemblies for `IPublicAPI` compatibility
---
## CI/CD
GitHub Actions workflow (`.github/workflows/dotnet.yml`):
- Runs on Windows
- Uses .NET 9.0
- Builds Release configuration
- Runs tests
- Creates installer via Squirrel
Key environment variables:
- `FlowVersion`: Version number (e.g., "1.20.2")
- `BUILD_NUMBER`: CI build number
---
## Resources
- **Migration checklist**: `AVALONIA_MIGRATION_CHECKLIST.md`
- **Plugin SDK docs**: `Flow.Launcher.Plugin/README.md`
- **EditorConfig**: `.editorconfig` for code style
- **XAML formatting**: `Settings.XamlStyler`

View file

@ -67,7 +67,15 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<AvaloniaResource Include="Resources\SegoeFluentIcons.ttf" />
<Content Include="Resources\SegoeFluentIcons.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<AvaloniaResource Include="..\Flow.Launcher\Resources\app.ico" Link="Images\app.ico" />
</ItemGroup>
<!-- WPF Resources for legacy plugin settings -->
<ItemGroup>
<Resource Include="WpfResources\*.xaml" />
</ItemGroup>
</Project>

View file

@ -9,8 +9,81 @@ internal sealed class Program
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static void Main(string[] args)
{
// Initialize WPF Application for plugins that rely on Application.Current.Resources
if (System.Windows.Application.Current == null)
{
var app = new System.Windows.Application
{
ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown
};
// Add common resources expected by plugins
// We load the copied WPF resources
try
{
// Load base theme resources (Colors like Color01B, etc.)
// TODO: Sync this with Avalonia theme (Light/Dark)
var themeDict = new System.Windows.ResourceDictionary
{
Source = new Uri("pack://application:,,,/Flow.Launcher.Avalonia;component/WpfResources/Dark.xaml")
};
app.Resources.MergedDictionaries.Add(themeDict);
var dict = new System.Windows.ResourceDictionary
{
Source = new Uri("pack://application:,,,/Flow.Launcher.Avalonia;component/WpfResources/CustomControlTemplate.xaml")
};
app.Resources.MergedDictionaries.Add(dict);
var dict2 = new System.Windows.ResourceDictionary
{
Source = new Uri("pack://application:,,,/Flow.Launcher.Avalonia;component/WpfResources/SettingWindowStyle.xaml")
};
app.Resources.MergedDictionaries.Add(dict2);
}
catch (Exception ex)
{
// Fallback if loading fails - at least define the margin that caused the crash
System.Diagnostics.Debug.WriteLine($"Failed to load WPF resources: {ex}");
var inner = ex.InnerException;
while (inner != null)
{
System.Diagnostics.Debug.WriteLine($"Inner: {inner}");
inner = inner.InnerException;
}
if (!app.Resources.Contains("SettingPanelMargin"))
{
app.Resources.Add("SettingPanelMargin", new System.Windows.Thickness(70, 13.5, 18, 13.5));
}
if (!app.Resources.Contains("SettingPanelItemTopBottomMargin"))
{
app.Resources.Add("SettingPanelItemTopBottomMargin", new System.Windows.Thickness(0, 4.5, 0, 4.5));
}
if (!app.Resources.Contains("SettingPanelItemRightMargin"))
{
app.Resources.Add("SettingPanelItemRightMargin", new System.Windows.Thickness(0, 0, 9, 0));
}
if (!app.Resources.Contains("SettingPanelItemLeftMargin"))
{
app.Resources.Add("SettingPanelItemLeftMargin", new System.Windows.Thickness(9, 0, 0, 0));
}
if (!app.Resources.Contains("SettingPanelItemLeftTopBottomMargin"))
{
app.Resources.Add("SettingPanelItemLeftTopBottomMargin", new System.Windows.Thickness(9, 4.5, 0, 4.5));
}
if (!app.Resources.Contains("SettingPanelItemRightTopBottomMargin"))
{
app.Resources.Add("SettingPanelItemRightTopBottomMargin", new System.Windows.Thickness(0, 4.5, 9, 4.5));
}
}
}
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()

View file

@ -3,9 +3,11 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
using Flow.Launcher.Avalonia.Views.Controls;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Controls;
namespace Flow.Launcher.Avalonia.ViewModel.SettingPages;
@ -42,35 +44,56 @@ public partial class PluginsSettingsViewModel : ObservableObject
public partial class PluginItemViewModel : ObservableObject
{
private readonly PluginPair _plugin;
private readonly ISettingProvider? _settingProvider;
public PluginItemViewModel(PluginPair plugin)
{
_plugin = plugin;
// Check if plugin has settings - for JsonRPC plugins, also check NeedCreateSettingPanel()
if (plugin.Plugin is ISettingProvider settingProvider)
{
try
// JsonRPC plugins may not have settings even if they implement ISettingProvider
if (plugin.Plugin is JsonRPCPluginBase jsonRpcPlugin)
{
// Create the WPF settings panel
SettingControl = settingProvider.CreateSettingPanel();
HasSettings = SettingControl != null;
if (jsonRpcPlugin.NeedCreateSettingPanel())
{
_settingProvider = settingProvider;
HasSettings = true;
}
}
catch (System.Exception)
else
{
// TODO: Log error using logger
HasSettings = false;
_settingProvider = settingProvider;
HasSettings = true;
}
}
}
[ObservableProperty]
private object? _settingControl;
[ObservableProperty]
private bool _hasSettings;
[ObservableProperty]
private bool _isExpanded;
[RelayCommand]
private void OpenSettings()
{
if (_settingProvider == null) return;
try
{
// Create the WPF settings panel on demand
var settingsControl = _settingProvider.CreateSettingPanel();
if (settingsControl != null)
{
WpfSettingsWindow.Show(settingsControl, Name);
}
}
catch (System.Exception ex)
{
// Log the error so we can diagnose issues
System.Diagnostics.Debug.WriteLine($"Failed to open settings for {Name}: {ex}");
Flow.Launcher.Infrastructure.Logger.Log.Exception(nameof(PluginItemViewModel), $"Failed to open settings for {Name}", ex);
}
}
public string Name => _plugin.Metadata.Name;
public string Description => _plugin.Metadata.Description;

View file

@ -1,61 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
using System.Windows.Interop;
using System.Windows;
namespace Flow.Launcher.Avalonia.Views.Controls
{
public class WpfControlHost : NativeControlHost
{
private HwndSource? _source;
public static readonly StyledProperty<object?> ContentProperty =
AvaloniaProperty.Register<WpfControlHost, object?>(nameof(Content));
public object? Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
var parameters = new HwndSourceParameters
{
ParentWindow = parent.Handle,
WindowStyle = 0x50000000, // WS_CHILD | WS_VISIBLE
PositionX = 0,
PositionY = 0,
};
_source = new HwndSource(parameters);
if (Content != null)
{
_source.RootVisual = Content as System.Windows.Media.Visual;
}
return new PlatformHandle(_source.Handle, "HwndSource");
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
_source?.Dispose();
_source = null;
base.DestroyNativeControlCore(control);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ContentProperty && _source != null)
{
_source.RootVisual = change.NewValue as System.Windows.Media.Visual;
}
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Flow.Launcher.Avalonia.Views.Controls;
/// <summary>
/// A standalone WPF Window that hosts plugin settings controls.
/// This avoids scrolling and rendering issues with embedded HwndSource.
/// </summary>
public class WpfSettingsWindow : Window
{
public WpfSettingsWindow(Control settingsControl, string pluginName)
{
Title = $"{pluginName} Settings";
Width = 800;
Height = 600;
MinWidth = 400;
MinHeight = 300;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
// Set proper background to avoid black background issue
Background = SystemColors.ControlBrush;
// Wrap in a ScrollViewer for proper scrolling
var scrollViewer = new ScrollViewer
{
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
Content = settingsControl,
Padding = new Thickness(10)
};
Content = scrollViewer;
}
/// <summary>
/// Shows the settings window for the given plugin.
/// </summary>
public static void Show(Control settingsControl, string pluginName)
{
var window = new WpfSettingsWindow(settingsControl, pluginName);
window.Show();
}
/// <summary>
/// Shows the settings window as a modal dialog.
/// </summary>
public static void ShowDialog(Control settingsControl, string pluginName)
{
var window = new WpfSettingsWindow(settingsControl, pluginName);
window.ShowDialog();
}
}

View file

@ -5,7 +5,6 @@
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vm="using:Flow.Launcher.Avalonia.ViewModel.SettingPages"
xmlns:i18n="using:Flow.Launcher.Avalonia.Resource"
xmlns:controls="using:Flow.Launcher.Avalonia.Views.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="Flow.Launcher.Avalonia.Views.SettingPages.PluginsSettingsPage"
x:DataType="vm:PluginsSettingsViewModel">
@ -24,8 +23,7 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:PluginItemViewModel">
<StackPanel Spacing="5">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,5">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,5">
<Image Grid.Column="0" Source="{Binding IconPath}" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
@ -38,29 +36,17 @@
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
<ToggleButton IsChecked="{Binding IsExpanded}"
IsVisible="{Binding HasSettings}"
ToolTip.Tip="Settings">
<Button Command="{Binding OpenSettingsCommand}"
IsVisible="{Binding HasSettings}"
ToolTip.Tip="Settings">
<ui:SymbolIcon Symbol="Settings" />
</ToggleButton>
</Button>
<ToggleSwitch IsChecked="{Binding !IsDisabled}"
OnContent="" OffContent="" />
</StackPanel>
</Grid>
<!-- Settings Panel -->
<Border IsVisible="{Binding IsExpanded}"
Background="{DynamicResource SolidBackgroundFillColorBase}"
BorderBrush="{DynamicResource ControlElevationBorderBrush}"
BorderThickness="1"
CornerRadius="4"
Padding="10"
Margin="48,0,0,10">
<controls:WpfControlHost Content="{Binding SettingControl}" Height="500" />
</Border>
</StackPanel>
</DataTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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