mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Add initial Avalonia UI project for migration
Create Flow.Launcher.Avalonia project as foundation for migrating from WPF to Avalonia UI framework. Key components: - MainWindow with query box and results list (matching WPF layout) - ViewModels: MainViewModel, ResultsViewModel, ResultViewModel - Themes/Base.axaml with converted styles from WPF - FluentAvaloniaUI for Windows 11 styling - References existing Core/Infrastructure/Plugin projects The project builds and runs alongside the existing WPF application. This is Phase 1 of the incremental migration approach.
This commit is contained in:
parent
229987ee90
commit
4120407ac3
17 changed files with 1231 additions and 13 deletions
21
Flow.Launcher.Avalonia/App.axaml
Normal file
21
Flow.Launcher.Avalonia/App.axaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Flow.Launcher.Avalonia"
|
||||
x:Class="Flow.Launcher.Avalonia.App"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant.
|
||||
"Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Flow.Launcher.Avalonia/Themes/Base.axaml"/>
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Additional resources can be added here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
52
Flow.Launcher.Avalonia/App.axaml.cs
Normal file
52
Flow.Launcher.Avalonia/App.axaml.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace Flow.Launcher.Avalonia;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// Set up dependency injection
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
Ioc.Default.ConfigureServices(serviceProvider);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Register settings - for now create a default instance
|
||||
// In production, this would load from the existing settings file
|
||||
services.AddSingleton<Settings>(_ =>
|
||||
{
|
||||
var settings = new Settings();
|
||||
// Set some defaults for the Avalonia version
|
||||
settings.WindowSize = 580;
|
||||
settings.WindowHeightSize = 42;
|
||||
settings.QueryBoxFontSize = 24;
|
||||
settings.ItemHeightSize = 50;
|
||||
settings.ResultItemFontSize = 14;
|
||||
settings.ResultSubItemFontSize = 12;
|
||||
settings.MaxResultsToShow = 6;
|
||||
return settings;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a boolean value to IsVisible (Avalonia uses bool for visibility, not Visibility enum)
|
||||
/// </summary>
|
||||
public class BoolToIsVisibleConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, inverts the boolean value (true becomes false, false becomes true)
|
||||
/// </summary>
|
||||
public bool Invert { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return Invert ? !boolValue : boolValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return Invert ? !boolValue : boolValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
109
Flow.Launcher.Avalonia/Converters/CommonConverters.cs
Normal file
109
Flow.Launcher.Avalonia/Converters/CommonConverters.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts text with highlight ranges to formatted text with bold highlights.
|
||||
/// This is a simplified version - full implementation would use Avalonia's TextDecorations.
|
||||
/// </summary>
|
||||
public class HighlightTextConverter : IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
// For now, just return the plain text
|
||||
// Full implementation would create formatted inline text with highlights
|
||||
if (values.Count >= 1 && values[0] is string text)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts query text and selected item to suggestion text.
|
||||
/// </summary>
|
||||
public class QuerySuggestionBoxConverter : IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
// values[0]: QueryTextBox (element)
|
||||
// values[1]: SelectedItem
|
||||
// values[2]: QueryText
|
||||
|
||||
if (values.Count < 3)
|
||||
return string.Empty;
|
||||
|
||||
var queryText = values[2] as string ?? string.Empty;
|
||||
|
||||
// For now, return empty - full implementation would show autocomplete suggestion
|
||||
// based on the selected result's title
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts integer index to ordinal number for hotkey display (1, 2, 3...).
|
||||
/// </summary>
|
||||
public class OrdinalConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is int index)
|
||||
{
|
||||
// Convert 0-based index to 1-based display, wrapping 9 to 0
|
||||
var displayNumber = (index + 1) % 10;
|
||||
return displayNumber.ToString();
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a size to a ratio of itself.
|
||||
/// </summary>
|
||||
public class SizeRatioConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double size && parameter is string ratioStr && double.TryParse(ratioStr, out var ratio))
|
||||
{
|
||||
return size * ratio;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts string to null if empty (for image sources).
|
||||
/// </summary>
|
||||
public class StringToNullConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string str && string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
65
Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
Normal file
65
Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ApplicationIcon>..\Flow.Launcher\Resources\app.ico</ApplicationIcon>
|
||||
<StartupObject>Flow.Launcher.Avalonia.Program</StartupObject>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<RootNamespace>Flow.Launcher.Avalonia</RootNamespace>
|
||||
<AssemblyName>Flow.Launcher.Avalonia</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\Output\Debug\Avalonia\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;AVALONIA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\Output\Release\Avalonia\</OutputPath>
|
||||
<DefineConstants>TRACE;RELEASE;AVALONIA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SolutionAssemblyInfo.cs" Link="Properties\SolutionAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.2.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.3" Condition="'$(Configuration)' == 'Debug'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
|
||||
<ProjectReference Include="..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy themes and languages from WPF project -->
|
||||
<ItemGroup>
|
||||
<Content Include="..\Flow.Launcher\Languages\*.xaml" Link="Languages\%(Filename)%(Extension)">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\Flow.Launcher\Images\*.png" Link="Images\%(Filename)%(Extension)">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\Flow.Launcher\Images\*.ico" Link="Images\%(Filename)%(Extension)">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
131
Flow.Launcher.Avalonia/MainWindow.axaml
Normal file
131
Flow.Launcher.Avalonia/MainWindow.axaml
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<Window 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:local="using:Flow.Launcher.Avalonia"
|
||||
xmlns:views="using:Flow.Launcher.Avalonia.Views"
|
||||
xmlns:vm="using:Flow.Launcher.Avalonia.ViewModel"
|
||||
xmlns:converters="using:Flow.Launcher.Avalonia.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="400"
|
||||
x:Class="Flow.Launcher.Avalonia.MainWindow"
|
||||
x:DataType="vm:MainViewModel"
|
||||
x:Name="FlowMainWindow"
|
||||
Title="Flow Launcher"
|
||||
Width="580"
|
||||
MinWidth="400"
|
||||
MinHeight="30"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
CanResize="True"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
SystemDecorations="None"
|
||||
TransparencyLevelHint="AcrylicBlur"
|
||||
Background="Transparent"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome"
|
||||
SizeToContent="Height">
|
||||
|
||||
<Window.Resources>
|
||||
<converters:BoolToIsVisibleConverter x:Key="BoolToVisibilityConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding EscCommand}" />
|
||||
<KeyBinding Gesture="F5" Command="{Binding ReloadPluginDataCommand}" />
|
||||
<KeyBinding Gesture="Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Alt+Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Down" Command="{Binding SelectNextItemCommand}" />
|
||||
<KeyBinding Gesture="Up" Command="{Binding SelectPrevItemCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+N" Command="{Binding SelectNextItemCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+P" Command="{Binding SelectPrevItemCommand}" />
|
||||
<KeyBinding Gesture="Tab" Command="{Binding AutocompleteQueryCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+R" Command="{Binding ReQueryCommand}" />
|
||||
</Window.KeyBindings>
|
||||
|
||||
<!-- Main container with rounded corners and background -->
|
||||
<Border Name="WindowBorder"
|
||||
Classes="windowBorder"
|
||||
PointerPressed="OnWindowBorderPointerPressed">
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<!-- Query Box Area -->
|
||||
<Grid Name="QueryBoxArea">
|
||||
<Border Name="QueryBoxBg" Classes="queryBoxBg" MinHeight="30">
|
||||
<Grid>
|
||||
<!-- Query Suggestion Box (autocomplete hint) -->
|
||||
<TextBox Name="QueryTextSuggestionBox"
|
||||
Classes="querySuggestionBox"
|
||||
IsEnabled="False"
|
||||
IsHitTestVisible="False"
|
||||
Text="{Binding QuerySuggestionText, Mode=OneWay}" />
|
||||
|
||||
<!-- Main Query Text Box -->
|
||||
<TextBox Name="QueryTextBox"
|
||||
Classes="queryBox"
|
||||
Text="{Binding QueryText, Mode=TwoWay}"
|
||||
Watermark="Type to search..."
|
||||
AcceptsReturn="False"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Search Icon -->
|
||||
<Canvas Name="SearchIconCanvas"
|
||||
Classes="searchIconPosition"
|
||||
HorizontalAlignment="Right">
|
||||
<Path Name="SearchIcon"
|
||||
Classes="searchIcon"
|
||||
Data="{DynamicResource SearchIconGeometry}"
|
||||
Stretch="Uniform" />
|
||||
</Canvas>
|
||||
|
||||
<!-- Progress/Loading indicator -->
|
||||
<ProgressBar Name="ProgressBar"
|
||||
Classes="pendingLine"
|
||||
IsIndeterminate="True"
|
||||
IsVisible="{Binding IsQueryRunning}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Stretch"
|
||||
Height="2"
|
||||
Margin="12,0,12,0" />
|
||||
</Grid>
|
||||
|
||||
<!-- Separator between query and results -->
|
||||
<Rectangle Name="MiddleSeparator"
|
||||
Classes="separator"
|
||||
IsVisible="{Binding HasResults}" />
|
||||
|
||||
<!-- Results Area -->
|
||||
<Border Name="ResultAreaBorder" Classes="resultAreaBorder">
|
||||
<Grid Name="ResultPreviewArea">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="80" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="0" /> <!-- Preview panel, hidden for now -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Result List -->
|
||||
<StackPanel Name="ResultArea" Grid.Column="0">
|
||||
<views:ResultListBox Name="ResultListBox"
|
||||
DataContext="{Binding Results}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Preview Panel Separator (hidden for now) -->
|
||||
<GridSplitter Name="PreviewSplitter"
|
||||
Grid.Column="1"
|
||||
Width="5"
|
||||
Background="Transparent"
|
||||
IsVisible="False" />
|
||||
|
||||
<!-- Preview Panel (to be implemented) -->
|
||||
<Grid Name="PreviewPanel"
|
||||
Grid.Column="2"
|
||||
IsVisible="False">
|
||||
<!-- Preview content will go here -->
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Window>
|
||||
118
Flow.Launcher.Avalonia/MainWindow.axaml.cs
Normal file
118
Flow.Launcher.Avalonia/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Avalonia.ViewModel;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using System;
|
||||
|
||||
namespace Flow.Launcher.Avalonia;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private MainViewModel? _viewModel;
|
||||
private TextBox? _queryTextBox;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Create and set the ViewModel
|
||||
var settings = Ioc.Default.GetRequiredService<Settings>();
|
||||
_viewModel = new MainViewModel(settings);
|
||||
DataContext = _viewModel;
|
||||
|
||||
// Get reference to the query text box
|
||||
_queryTextBox = this.FindControl<TextBox>("QueryTextBox");
|
||||
|
||||
// Subscribe to window events
|
||||
this.Deactivated += OnWindowDeactivated;
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
// Focus the query text box when window loads
|
||||
_queryTextBox?.Focus();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
|
||||
// Center the window on screen
|
||||
CenterOnScreen();
|
||||
|
||||
// Focus and select all text
|
||||
if (_queryTextBox != null)
|
||||
{
|
||||
_queryTextBox.Focus();
|
||||
_queryTextBox.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void CenterOnScreen()
|
||||
{
|
||||
var screen = Screens.Primary;
|
||||
if (screen != null)
|
||||
{
|
||||
var workingArea = screen.WorkingArea;
|
||||
var x = (workingArea.Width - Width) / 2 + workingArea.X;
|
||||
var y = workingArea.Height * 0.25 + workingArea.Y; // Position at 25% from top (like Flow Launcher)
|
||||
Position = new PixelPoint((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
// Handle Escape to hide window
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
Hide();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowBorderPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
// Allow dragging the window
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: In Avalonia, use the Deactivated event instead of override
|
||||
// Subscribe in constructor: this.Deactivated += OnWindowDeactivated;
|
||||
private void OnWindowDeactivated(object? sender, EventArgs e)
|
||||
{
|
||||
// Optionally hide window when it loses focus (like original Flow Launcher)
|
||||
// Uncomment if desired:
|
||||
// Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the window and focuses the query text box
|
||||
/// </summary>
|
||||
public void ShowAndFocus()
|
||||
{
|
||||
Show();
|
||||
Activate();
|
||||
_queryTextBox?.Focus();
|
||||
_queryTextBox?.SelectAll();
|
||||
}
|
||||
}
|
||||
21
Flow.Launcher.Avalonia/Program.cs
Normal file
21
Flow.Launcher.Avalonia/Program.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
|
||||
namespace Flow.Launcher.Avalonia;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// 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);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
211
Flow.Launcher.Avalonia/Themes/Base.axaml
Normal file
211
Flow.Launcher.Avalonia/Themes/Base.axaml
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Styles.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="avares://Flow.Launcher.Avalonia/Themes/Resources.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
|
||||
<!-- Window Border Style -->
|
||||
<Style Selector="Border.windowBorder">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="#E6202020" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<!-- Query Box Background -->
|
||||
<Style Selector="Border.queryBoxBg">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="50" />
|
||||
</Style>
|
||||
|
||||
<!-- Query Text Box -->
|
||||
<Style Selector="TextBox.queryBox">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Margin" Value="16,7,60,7" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="#E3E0E3" />
|
||||
<Setter Property="CaretBrush" Value="#E3E0E3" />
|
||||
<Setter Property="SelectionBrush" Value="#505050" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="MinHeight" Value="42" />
|
||||
</Style>
|
||||
|
||||
<!-- Remove TextBox default styling -->
|
||||
<Style Selector="TextBox.queryBox:focus /template/ Border#PART_BorderElement">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.queryBox:pointerover /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.queryBox /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Query Suggestion Box (autocomplete hint) -->
|
||||
<Style Selector="TextBox.querySuggestionBox">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Margin" Value="16,7,60,7" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="#555555" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="MinHeight" Value="42" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.querySuggestionBox /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Search Icon -->
|
||||
<Style Selector="Path.searchIcon">
|
||||
<Setter Property="Fill" Value="#888888" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Canvas.searchIconPosition">
|
||||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Margin" Value="0,0,18,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- Separator -->
|
||||
<Style Selector="Rectangle.separator">
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Fill" Value="#333333" />
|
||||
<Setter Property="Margin" Value="12,0,12,0" />
|
||||
</Style>
|
||||
|
||||
<!-- Progress/Pending Line -->
|
||||
<Style Selector="ProgressBar.pendingLine">
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColor}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- Result Area Border -->
|
||||
<Style Selector="Border.resultAreaBorder">
|
||||
<Setter Property="CornerRadius" Value="0,0,8,8" />
|
||||
</Style>
|
||||
|
||||
<!-- Result ListBox -->
|
||||
<Style Selector="ListBox.resultListBox">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0,4,0,4" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||
</Style>
|
||||
|
||||
<!-- Result List Item -->
|
||||
<Style Selector="ListBoxItem.resultItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="4,2,4,2" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="MinHeight" Value="50" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem.resultItem:selected">
|
||||
<Setter Property="Background" Value="#30FFFFFF" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem.resultItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="#30FFFFFF" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem.resultItem:pointerover">
|
||||
<Setter Property="Background" Value="#20FFFFFF" />
|
||||
</Style>
|
||||
|
||||
<!-- Result Item Grid -->
|
||||
<Style Selector="Grid.resultItemGrid">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="46" />
|
||||
</Style>
|
||||
|
||||
<!-- Result Icon -->
|
||||
<Style Selector="Image.resultIcon">
|
||||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Margin" Value="12,0,0,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- Result Title -->
|
||||
<Style Selector="TextBlock.resultTitle">
|
||||
<Setter Property="Foreground" Value="#FFFFF8" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Margin" Value="0,0,0,1" />
|
||||
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
</Style>
|
||||
|
||||
<!-- Result SubTitle -->
|
||||
<Style Selector="TextBlock.resultSubTitle">
|
||||
<Setter Property="Foreground" Value="#999999" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Margin" Value="0,1,0,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
</Style>
|
||||
|
||||
<!-- Result Bullet/Selection Indicator -->
|
||||
<Style Selector="Border.resultBullet">
|
||||
<Setter Property="Width" Value="3" />
|
||||
<Setter Property="CornerRadius" Value="1.5" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Margin" Value="0,8,0,8" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem.resultItem:selected Border.resultBullet">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- Scrollbar Styles -->
|
||||
<Style Selector="ScrollBar">
|
||||
<Setter Property="Width" Value="6" />
|
||||
<Setter Property="Margin" Value="0,4,2,4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ScrollBar /template/ Thumb">
|
||||
<Setter Property="Background" Value="#505050" />
|
||||
<Setter Property="CornerRadius" Value="3" />
|
||||
<Setter Property="MinHeight" Value="20" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ScrollBar:pointerover /template/ Thumb">
|
||||
<Setter Property="Background" Value="#707070" />
|
||||
</Style>
|
||||
|
||||
<!-- Hotkey Badge -->
|
||||
<Style Selector="Border.hotkeyBadge">
|
||||
<Setter Property="Background" Value="#20FFFFFF" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Padding" Value="6,2,6,2" />
|
||||
<Setter Property="Margin" Value="0,0,10,0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.hotkeyText">
|
||||
<Setter Property="Foreground" Value="#888888" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
</Styles>
|
||||
23
Flow.Launcher.Avalonia/Themes/Resources.axaml
Normal file
23
Flow.Launcher.Avalonia/Themes/Resources.axaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Search Icon Geometry (same as WPF version) -->
|
||||
<StreamGeometry x:Key="SearchIconGeometry">F1 M12000,12000z M0,0z M10354,10962C10326,10951 10279,10927 10249,10907 10216,10886 9476,10153 8370,9046 7366,8042 6541,7220 6536,7220 6532,7220 6498,7242 6461,7268 6213,7447 5883,7619 5592,7721 5194,7860 4802,7919 4360,7906 3612,7886 2953,7647 2340,7174 2131,7013 1832,6699 1664,6465 1394,6088 1188,5618 1097,5170 1044,4909 1030,4764 1030,4470 1030,4130 1056,3914 1135,3609 1263,3110 1511,2633 1850,2235 1936,2134 2162,1911 2260,1829 2781,1395 3422,1120 4090,1045 4271,1025 4667,1025 4848,1045 5505,1120 6100,1368 6630,1789 6774,1903 7081,2215 7186,2355 7362,2588 7467,2759 7579,2990 7802,3455 7911,3937 7911,4460 7911,4854 7861,5165 7737,5542 7684,5702 7675,5724 7602,5885 7517,6071 7390,6292 7270,6460 7242,6499 7220,6533 7220,6538 7220,6542 8046,7371 9055,8380 10441,9766 10898,10229 10924,10274 10945,10308 10966,10364 10976,10408 10990,10472 10991,10493 10980,10554 10952,10717 10840,10865 10690,10937 10621,10971 10607,10974 10510,10977 10425,10980 10395,10977 10354,10962z M4685,7050C5214,7001 5694,6809 6100,6484 6209,6396 6396,6209 6484,6100 7151,5267 7246,4110 6721,3190 6369,2571 5798,2137 5100,1956 4706,1855 4222,1855 3830,1957 3448,2056 3140,2210 2838,2453 2337,2855 2010,3427 1908,4080 1877,4274 1877,4656 1908,4850 1948,5105 2028,5370 2133,5590 2459,6272 3077,6782 3810,6973 3967,7014 4085,7034 4290,7053 4371,7061 4583,7059 4685,7050z</StreamGeometry>
|
||||
|
||||
<!-- Common Colors -->
|
||||
<Color x:Key="WindowBackgroundColor">#E6202020</Color>
|
||||
<Color x:Key="QueryBoxForegroundColor">#E3E0E3</Color>
|
||||
<Color x:Key="ResultTitleColor">#FFFFF8</Color>
|
||||
<Color x:Key="ResultSubTitleColor">#999999</Color>
|
||||
<Color x:Key="SeparatorColor">#333333</Color>
|
||||
<Color x:Key="SelectedItemBackgroundColor">#30FFFFFF</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource WindowBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="QueryBoxForegroundBrush" Color="{StaticResource QueryBoxForegroundColor}" />
|
||||
<SolidColorBrush x:Key="ResultTitleBrush" Color="{StaticResource ResultTitleColor}" />
|
||||
<SolidColorBrush x:Key="ResultSubTitleBrush" Color="{StaticResource ResultSubTitleColor}" />
|
||||
<SolidColorBrush x:Key="SeparatorBrush" Color="{StaticResource SeparatorColor}" />
|
||||
<SolidColorBrush x:Key="SelectedItemBackgroundBrush" Color="{StaticResource SelectedItemBackgroundColor}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
151
Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs
Normal file
151
Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Simplified MainViewModel for the Avalonia version.
|
||||
/// This will eventually be unified with the WPF MainViewModel.
|
||||
/// </summary>
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _queryText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _querySuggestionText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isQueryRunning;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasResults;
|
||||
|
||||
[ObservableProperty]
|
||||
private ResultsViewModel _results;
|
||||
|
||||
public Settings Settings => _settings;
|
||||
|
||||
public MainViewModel(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_results = new ResultsViewModel(settings);
|
||||
|
||||
// Add some demo results for testing
|
||||
AddDemoResults();
|
||||
}
|
||||
|
||||
partial void OnQueryTextChanged(string value)
|
||||
{
|
||||
// Simulate query execution
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
IsQueryRunning = true;
|
||||
HasResults = true;
|
||||
|
||||
// Simulate search
|
||||
Task.Delay(100).ContinueWith(_ =>
|
||||
{
|
||||
IsQueryRunning = false;
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
else
|
||||
{
|
||||
HasResults = false;
|
||||
IsQueryRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDemoResults()
|
||||
{
|
||||
// Add demo results for UI testing
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Welcome to Flow Launcher (Avalonia)",
|
||||
SubTitle = "This is a demo result - Avalonia migration in progress",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Settings",
|
||||
SubTitle = "Open Flow Launcher settings",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Notepad",
|
||||
SubTitle = "C:\\Windows\\System32\\notepad.exe",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Calculator",
|
||||
SubTitle = "Microsoft Calculator",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
HasResults = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Esc()
|
||||
{
|
||||
QueryText = string.Empty;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenResult(object? parameter)
|
||||
{
|
||||
var selectedResult = Results.SelectedItem;
|
||||
if (selectedResult != null)
|
||||
{
|
||||
// Execute the result action
|
||||
System.Diagnostics.Debug.WriteLine($"Opening result: {selectedResult.Title}");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectNextItem()
|
||||
{
|
||||
Results.SelectNextItem();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectPrevItem()
|
||||
{
|
||||
Results.SelectPrevItem();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AutocompleteQuery()
|
||||
{
|
||||
if (Results.SelectedItem != null)
|
||||
{
|
||||
QueryText = Results.SelectedItem.Title;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ReloadPluginData()
|
||||
{
|
||||
// Placeholder for plugin data reload
|
||||
System.Diagnostics.Debug.WriteLine("Reloading plugin data...");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ReQuery()
|
||||
{
|
||||
// Placeholder for re-query
|
||||
System.Diagnostics.Debug.WriteLine("Re-querying...");
|
||||
}
|
||||
}
|
||||
30
Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs
Normal file
30
Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for a single result item.
|
||||
/// </summary>
|
||||
public partial class ResultViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string _title = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _subTitle = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _iconPath = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
[ObservableProperty]
|
||||
private Settings? _settings;
|
||||
|
||||
// Computed properties for display
|
||||
public bool ShowIcon => !string.IsNullOrEmpty(IconPath);
|
||||
|
||||
public bool ShowSubTitle => !string.IsNullOrEmpty(SubTitle);
|
||||
}
|
||||
91
Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs
Normal file
91
Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for the results list.
|
||||
/// </summary>
|
||||
public partial class ResultsViewModel : ObservableObject
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ResultViewModel> _results = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ResultViewModel? _selectedItem;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isVisible = true;
|
||||
|
||||
public Settings Settings => _settings;
|
||||
|
||||
public int MaxHeight => (int)(_settings.MaxResultsToShow * _settings.ItemHeightSize);
|
||||
|
||||
public ResultsViewModel(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public void AddResult(ResultViewModel result)
|
||||
{
|
||||
result.Settings = _settings;
|
||||
Results.Add(result);
|
||||
|
||||
// Select first item if nothing selected
|
||||
if (SelectedItem == null && Results.Count > 0)
|
||||
{
|
||||
SelectedIndex = 0;
|
||||
SelectedItem = Results[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Results.Clear();
|
||||
SelectedItem = null;
|
||||
SelectedIndex = -1;
|
||||
}
|
||||
|
||||
public void SelectNextItem()
|
||||
{
|
||||
if (Results.Count == 0) return;
|
||||
|
||||
var newIndex = SelectedIndex + 1;
|
||||
if (newIndex >= Results.Count)
|
||||
{
|
||||
newIndex = 0; // Wrap to beginning
|
||||
}
|
||||
|
||||
SelectedIndex = newIndex;
|
||||
SelectedItem = Results[newIndex];
|
||||
}
|
||||
|
||||
public void SelectPrevItem()
|
||||
{
|
||||
if (Results.Count == 0) return;
|
||||
|
||||
var newIndex = SelectedIndex - 1;
|
||||
if (newIndex < 0)
|
||||
{
|
||||
newIndex = Results.Count - 1; // Wrap to end
|
||||
}
|
||||
|
||||
SelectedIndex = newIndex;
|
||||
SelectedItem = Results[newIndex];
|
||||
}
|
||||
|
||||
partial void OnSelectedIndexChanged(int value)
|
||||
{
|
||||
if (value >= 0 && value < Results.Count)
|
||||
{
|
||||
SelectedItem = Results[value];
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Flow.Launcher.Avalonia/Views/ResultListBox.axaml
Normal file
93
Flow.Launcher.Avalonia/Views/ResultListBox.axaml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<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:vm="using:Flow.Launcher.Avalonia.ViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="580" d:DesignHeight="300"
|
||||
x:Class="Flow.Launcher.Avalonia.Views.ResultListBox"
|
||||
x:DataType="vm:ResultsViewModel">
|
||||
|
||||
<ListBox Name="ResultsList"
|
||||
Classes="resultListBox"
|
||||
ItemsSource="{Binding Results}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
|
||||
MaxHeight="{Binding MaxHeight}"
|
||||
SelectionMode="Single"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ResultViewModel">
|
||||
<Grid Classes="resultItemGrid" ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- Selection Bullet -->
|
||||
<Border Grid.Column="0"
|
||||
Classes="resultBullet"
|
||||
VerticalAlignment="Stretch" />
|
||||
|
||||
<!-- Icon and Text -->
|
||||
<Grid Grid.Column="1" ColumnDefinitions="Auto,*" Margin="6,0,0,0">
|
||||
|
||||
<!-- Icon -->
|
||||
<Image Grid.Column="0"
|
||||
Classes="resultIcon"
|
||||
Source="{Binding IconPath, TargetNullValue={x:Null}}"
|
||||
IsVisible="{Binding ShowIcon}"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality" />
|
||||
|
||||
<!-- Title and SubTitle -->
|
||||
<Grid Grid.Column="1"
|
||||
RowDefinitions="Auto,Auto"
|
||||
Margin="10,0,10,0"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Classes="resultTitle"
|
||||
Text="{Binding Title}"
|
||||
ToolTip.Tip="{Binding Title}" />
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Classes="resultSubTitle"
|
||||
Text="{Binding SubTitle}"
|
||||
IsVisible="{Binding ShowSubTitle}"
|
||||
ToolTip.Tip="{Binding SubTitle}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Hotkey Badge (optional, can be enabled later) -->
|
||||
<!--
|
||||
<Border Grid.Column="2"
|
||||
Classes="hotkeyBadge"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Classes="hotkeyText" Text="Alt+1" />
|
||||
</Border>
|
||||
-->
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
||||
<ListBox.ItemContainerTheme>
|
||||
<ControlTheme TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="4,2,4,2" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="MinHeight" Value="50" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
|
||||
<Style Selector="^:selected">
|
||||
<Setter Property="Background" Value="#30FFFFFF" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="#30FFFFFF" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:pointerover">
|
||||
<Setter Property="Background" Value="#20FFFFFF" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ListBox.ItemContainerTheme>
|
||||
|
||||
</ListBox>
|
||||
</UserControl>
|
||||
34
Flow.Launcher.Avalonia/Views/ResultListBox.axaml.cs
Normal file
34
Flow.Launcher.Avalonia/Views/ResultListBox.axaml.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.Views;
|
||||
|
||||
public partial class ResultListBox : UserControl
|
||||
{
|
||||
private ListBox? _listBox;
|
||||
|
||||
public ResultListBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
_listBox = this.FindControl<ListBox>("ResultsList");
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
|
||||
// Handle left click on result item
|
||||
var point = e.GetCurrentPoint(this);
|
||||
if (point.Properties.IsLeftButtonPressed)
|
||||
{
|
||||
// The ListBox handles selection automatically
|
||||
// Additional click handling can be added here
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Flow.Launcher.Avalonia/app.manifest
Normal file
19
Flow.Launcher.Avalonia/app.manifest
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Flow.Launcher.Avalonia"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
</assembly>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32901.215
|
||||
|
|
@ -71,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Plugin
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.WindowsSettings", "Plugins\Flow.Launcher.Plugin.WindowsSettings\Flow.Launcher.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flow.Launcher.Avalonia", "Flow.Launcher.Avalonia\Flow.Launcher.Avalonia.csproj", "{6B30B56B-7CEA-4868-828D-1460A57ACF47}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -81,8 +84,20 @@ Global
|
|||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -105,18 +120,6 @@ Global
|
|||
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -286,6 +289,18 @@ Global
|
|||
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6B30B56B-7CEA-4868-828D-1460A57ACF47}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
Loading…
Reference in a new issue