mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
refactor: move history storage to infrastructure and implement WPF settings window host in Avalonia
This commit is contained in:
parent
0e1af2279b
commit
fb9721c7f2
13 changed files with 10507 additions and 95 deletions
430
AGENTS.md
Normal file
430
AGENTS.md
Normal 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`
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Flow.Launcher.Avalonia/Views/Controls/WpfSettingsWindow.cs
Normal file
55
Flow.Launcher.Avalonia/Views/Controls/WpfSettingsWindow.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
6102
Flow.Launcher.Avalonia/WpfResources/CustomControlTemplate.xaml
Normal file
6102
Flow.Launcher.Avalonia/WpfResources/CustomControlTemplate.xaml
Normal file
File diff suppressed because it is too large
Load diff
1671
Flow.Launcher.Avalonia/WpfResources/Dark.xaml
Normal file
1671
Flow.Launcher.Avalonia/WpfResources/Dark.xaml
Normal file
File diff suppressed because it is too large
Load diff
1670
Flow.Launcher.Avalonia/WpfResources/Light.xaml
Normal file
1670
Flow.Launcher.Avalonia/WpfResources/Light.xaml
Normal file
File diff suppressed because it is too large
Load diff
455
Flow.Launcher.Avalonia/WpfResources/SettingWindowStyle.xaml
Normal file
455
Flow.Launcher.Avalonia/WpfResources/SettingWindowStyle.xaml
Normal 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>
|
||||
Loading…
Reference in a new issue