diff --git a/Splitter-UI/App.axaml b/Splitter-UI/App.axaml
new file mode 100644
index 0000000..f167199
--- /dev/null
+++ b/Splitter-UI/App.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Splitter-UI/App.axaml.cs b/Splitter-UI/App.axaml.cs
new file mode 100644
index 0000000..95bf3a3
--- /dev/null
+++ b/Splitter-UI/App.axaml.cs
@@ -0,0 +1,37 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using Microsoft.Extensions.DependencyInjection;
+using Splitter_UI.Views;
+
+namespace Splitter_UI;
+
+public partial class App : Application
+{
+ private readonly ServiceProvider _provider;
+
+ public App(ServiceProvider provider)
+ {
+ _provider = provider;
+ }
+
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ var vm = _provider.GetRequiredService();
+
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = vm
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/Splitter-UI/Assets/avalonia-logo.ico b/Splitter-UI/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..f7da8bb
Binary files /dev/null and b/Splitter-UI/Assets/avalonia-logo.ico differ
diff --git a/Splitter-UI/GlobalUsing.cs b/Splitter-UI/GlobalUsing.cs
new file mode 100644
index 0000000..4377859
--- /dev/null
+++ b/Splitter-UI/GlobalUsing.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Threading.Tasks;
+
+global using splitter;
+global using Splitter_UI.Models;
+global using Splitter_UI.Services;
+global using Splitter_UI.ViewModels;
diff --git a/Splitter-UI/Models/PreviewData.cs b/Splitter-UI/Models/PreviewData.cs
new file mode 100644
index 0000000..23af86d
--- /dev/null
+++ b/Splitter-UI/Models/PreviewData.cs
@@ -0,0 +1,11 @@
+using Avalonia;
+
+namespace Splitter_UI.Models;
+
+public sealed class PreviewData
+{
+ public Avalonia.Media.Imaging.Bitmap? Frame { get; init; }
+ public IReadOnlyList FaceBoxes { get; init; } = [];
+ public IReadOnlyList BodyBoxes { get; init; } = [];
+ public Rect? CropRect { get; init; }
+}
diff --git a/Splitter-UI/Models/ProgressInfo.cs b/Splitter-UI/Models/ProgressInfo.cs
new file mode 100644
index 0000000..02cebc8
--- /dev/null
+++ b/Splitter-UI/Models/ProgressInfo.cs
@@ -0,0 +1,6 @@
+namespace Splitter_UI.Models;
+
+public class ProgressInfo
+{
+ public double Percent { get; set; }
+}
diff --git a/Splitter-UI/Program.cs b/Splitter-UI/Program.cs
new file mode 100644
index 0000000..d435954
--- /dev/null
+++ b/Splitter-UI/Program.cs
@@ -0,0 +1,54 @@
+using Avalonia;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Splitter_UI;
+
+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)
+ {
+ var services = ConfigureServices();
+ var provider = services.BuildServiceProvider();
+
+ BuildAvaloniaApp(provider)
+ .StartWithClassicDesktopLifetime(args);
+ }
+
+ private static ServiceCollection ConfigureServices()
+ {
+ var services = new ServiceCollection();
+
+ // ViewModels
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ // Domain services (your pipeline)
+ services.AddTransient();
+ services.AddTransient();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+
+ return services;
+ }
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp(ServiceProvider provider)
+ => AppBuilder.Configure(() => new App(provider))
+ .UsePlatformDetect()
+#if DEBUG
+ .WithDeveloperTools()
+#endif
+ .WithInterFont()
+ .LogToTrace();
+}
diff --git a/Splitter-UI/Services/AutoDecisionService.cs b/Splitter-UI/Services/AutoDecisionService.cs
new file mode 100644
index 0000000..2887eb6
--- /dev/null
+++ b/Splitter-UI/Services/AutoDecisionService.cs
@@ -0,0 +1,8 @@
+namespace Splitter_UI.Services;
+
+public sealed class AutoDecisionService : IAutoDecisionService
+{
+ public void ApplyAutoDecisions(SingleJob job, VideoInfo probe)
+ {
+ }
+}
diff --git a/Splitter-UI/Services/FileJobFactory.cs b/Splitter-UI/Services/FileJobFactory.cs
new file mode 100644
index 0000000..921a07c
--- /dev/null
+++ b/Splitter-UI/Services/FileJobFactory.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.DependencyInjection;
+
+public sealed class FileJobFactory : IFileJobFactory
+{
+ private readonly IServiceProvider _services;
+
+ public FileJobFactory(IServiceProvider services)
+ {
+ _services = services;
+ }
+
+ public FileJobViewModel Create(SingleJob job)
+ {
+ // Resolve a fresh VM + fresh services
+ return ActivatorUtilities.CreateInstance(_services, job);
+ }
+}
diff --git a/Splitter-UI/Services/FileProbeService.cs b/Splitter-UI/Services/FileProbeService.cs
new file mode 100644
index 0000000..fbb88d0
--- /dev/null
+++ b/Splitter-UI/Services/FileProbeService.cs
@@ -0,0 +1,10 @@
+namespace Splitter_UI.Services;
+
+public sealed class FileProbeService : IFileProbeService
+{
+ public async Task ProbeAsync(SingleJob job)
+ {
+ var res = await Task.Run(() =>ProbeVideo.Probe(job));
+ return res;
+ }
+}
diff --git a/Splitter-UI/Services/IAutoDecisionService.cs b/Splitter-UI/Services/IAutoDecisionService.cs
new file mode 100644
index 0000000..5744321
--- /dev/null
+++ b/Splitter-UI/Services/IAutoDecisionService.cs
@@ -0,0 +1,6 @@
+namespace Splitter_UI.Services;
+
+public interface IAutoDecisionService
+{
+ void ApplyAutoDecisions(SingleJob job, VideoInfo probe);
+}
diff --git a/Splitter-UI/Services/IFileJobFactory.cs b/Splitter-UI/Services/IFileJobFactory.cs
new file mode 100644
index 0000000..883cdb3
--- /dev/null
+++ b/Splitter-UI/Services/IFileJobFactory.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Splitter_UI.Services;
+
+public interface IFileJobFactory
+{
+ FileJobViewModel Create(SingleJob job);
+}
diff --git a/Splitter-UI/Services/IFileProbeService.cs b/Splitter-UI/Services/IFileProbeService.cs
new file mode 100644
index 0000000..33a284c
--- /dev/null
+++ b/Splitter-UI/Services/IFileProbeService.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Splitter_UI.Services;
+
+public interface IFileProbeService
+{
+ Task ProbeAsync(SingleJob job);
+}
diff --git a/Splitter-UI/Services/ILogService.cs b/Splitter-UI/Services/ILogService.cs
new file mode 100644
index 0000000..048f375
--- /dev/null
+++ b/Splitter-UI/Services/ILogService.cs
@@ -0,0 +1,9 @@
+
+namespace Splitter_UI.Services;
+
+public interface ILogService
+{
+ event Action? MessageLogged;
+
+ void Write(string message);
+}
diff --git a/Splitter-UI/Services/IProcessingService.cs b/Splitter-UI/Services/IProcessingService.cs
new file mode 100644
index 0000000..49f9e93
--- /dev/null
+++ b/Splitter-UI/Services/IProcessingService.cs
@@ -0,0 +1,11 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Splitter_UI.Services;
+
+public interface IProcessingService
+{
+ event Action? ProgressChanged;
+
+ Task ProcessAsync(IEnumerable jobs, CancellationToken token);
+}
diff --git a/Splitter-UI/Services/IThumbnailService.cs b/Splitter-UI/Services/IThumbnailService.cs
new file mode 100644
index 0000000..6c5ee75
--- /dev/null
+++ b/Splitter-UI/Services/IThumbnailService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using Avalonia.Media.Imaging;
+
+namespace Splitter_UI.Services;
+
+public interface IThumbnailService
+{
+ Task CreateThumbnailAsync(string file, VideoInfo probe);
+}
diff --git a/Splitter-UI/Services/LogService.cs b/Splitter-UI/Services/LogService.cs
new file mode 100644
index 0000000..f29382a
--- /dev/null
+++ b/Splitter-UI/Services/LogService.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Avalonia;
+
+namespace Splitter_UI.Services;
+
+public sealed class LogService : ILogService
+{
+ public event Action? MessageLogged;
+
+ public void Write(string message)
+ {
+ MessageLogged?.Invoke(message);
+ }
+}
diff --git a/Splitter-UI/Services/ProcessingService.cs b/Splitter-UI/Services/ProcessingService.cs
new file mode 100644
index 0000000..8fe779d
--- /dev/null
+++ b/Splitter-UI/Services/ProcessingService.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Splitter_UI.Services;
+
+public sealed class ProcessingService : IProcessingService
+{
+ public event Action? ProgressChanged;
+
+ public async Task ProcessAsync(IEnumerable jobs, CancellationToken token)
+ {
+ foreach (var job in jobs)
+ {
+ for (int i = 0; i <= 100; i += 20)
+ {
+ if (token.IsCancellationRequested)
+ return;
+
+ var progress = new ProgressInfo { Percent = i };
+
+ // Notify UI
+ ProgressChanged?.Invoke(job.InputFile, progress);
+
+ await Task.Delay(100, token);
+ }
+ }
+ }
+}
diff --git a/Splitter-UI/Services/ThumbnailService.cs b/Splitter-UI/Services/ThumbnailService.cs
new file mode 100644
index 0000000..86443a8
--- /dev/null
+++ b/Splitter-UI/Services/ThumbnailService.cs
@@ -0,0 +1,120 @@
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+
+namespace Splitter_UI.Services;
+
+public sealed class ThumbnailService : IThumbnailService
+{
+ private readonly int _thumbWidth = 160;
+ private readonly int _thumbHeight = 90;
+
+ // Reusable buffer for BGR24 → 3 bytes per pixel
+ private readonly byte[] _bgrBuffer;
+ private readonly byte[] _bgraBuffer;
+
+ public ThumbnailService()
+ {
+ _bgrBuffer = new byte[_thumbWidth * _thumbHeight * 3];
+ _bgraBuffer = new byte[_thumbWidth * _thumbHeight * 4];
+ }
+
+ public async Task CreateThumbnailAsync(string file, VideoInfo probe)
+ {
+ // Decode a single frame using ffmpeg → raw BGR24 into _bgrBuffer
+ bool ok = await DecodeFrameAsync(file);
+ if (!ok)
+ return null;
+
+ // Convert BGR24 → BGRA32
+ ConvertBgrToBgra(_bgrBuffer, _bgraBuffer, _thumbWidth, _thumbHeight);
+
+ // Create Avalonia Bitmap
+ return CreateBitmap(_bgraBuffer, _thumbWidth, _thumbHeight);
+ }
+
+ private async Task DecodeFrameAsync(string file)
+ {
+ // ffmpeg command: decode one frame, resize, output raw BGR24
+ var args =
+ $"-ss 0 -t 0.1 -i \"{file}\" " +
+ "-an -sn " +
+ $"-vf \"scale={_thumbWidth}:{_thumbHeight}:force_original_aspect_ratio=decrease," +
+ $"pad={_thumbWidth}:{_thumbHeight}:(ow-iw)/2:(oh-ih)/2,format=bgr24\" " +
+ "-f rawvideo -";
+
+ var psi = new ProcessStartInfo
+ {
+ FileName = "ffmpeg",
+ Arguments = args,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ var p = new Process { StartInfo = psi };
+ p.Start();
+
+ int needed = _bgrBuffer.Length;
+ int read = 0;
+
+ using var stdout = p.StandardOutput.BaseStream;
+
+ while (read < needed)
+ {
+ int r = await stdout.ReadAsync(_bgrBuffer, read, needed - read);
+ if (r == 0)
+ {
+ TryKill(p);
+ return false;
+ }
+ read += r;
+ }
+
+ TryKill(p);
+ return true;
+ }
+
+ private static void TryKill(Process p)
+ {
+ try { p.Kill(); } catch { }
+ }
+
+ private static void ConvertBgrToBgra(byte[] bgr, byte[] bgra, int width, int height)
+ {
+ int si = 0;
+ int di = 0;
+
+ int totalPixels = width * height;
+
+ for (int i = 0; i < totalPixels; i++)
+ {
+ bgra[di + 0] = bgr[si + 0]; // B
+ bgra[di + 1] = bgr[si + 1]; // G
+ bgra[di + 2] = bgr[si + 2]; // R
+ bgra[di + 3] = 255; // A
+
+ si += 3;
+ di += 4;
+ }
+ }
+
+ private static unsafe Bitmap CreateBitmap(byte[] bgra, int width, int height)
+ {
+ int stride = width * 4;
+
+ fixed (byte* p = bgra)
+ {
+ return new Bitmap(
+ PixelFormat.Bgra8888,
+ AlphaFormat.Premul,
+ (nint)p,
+ new PixelSize(width, height),
+ new Vector(96, 96),
+ stride);
+ }
+ }
+
+}
diff --git a/Splitter-UI/Splitter-UI.csproj b/Splitter-UI/Splitter-UI.csproj
new file mode 100644
index 0000000..60aaf87
--- /dev/null
+++ b/Splitter-UI/Splitter-UI.csproj
@@ -0,0 +1,35 @@
+
+
+ WinExe
+ net10.0
+ enable
+ enable
+ latest
+ true
+ x64
+ win-x64
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/ViewLocator.cs b/Splitter-UI/ViewLocator.cs
new file mode 100644
index 0000000..ff90ea9
--- /dev/null
+++ b/Splitter-UI/ViewLocator.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+
+namespace Splitter_UI;
+///
+/// Given a view model, returns the corresponding view if possible.
+///
+[RequiresUnreferencedCode(
+ "Default implementation of ViewLocator involves reflection which may be trimmed away.",
+ Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
+public class ViewLocator : IDataTemplate
+{
+ public Control? Build(object? param)
+ {
+ if (param is null)
+ return null;
+
+ var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ public bool Match(object? data)
+ {
+ return data is ViewModelBase;
+ }
+}
diff --git a/Splitter-UI/ViewModels/FileJobViewModel.cs b/Splitter-UI/ViewModels/FileJobViewModel.cs
new file mode 100644
index 0000000..cc8d896
--- /dev/null
+++ b/Splitter-UI/ViewModels/FileJobViewModel.cs
@@ -0,0 +1,41 @@
+using Avalonia.Media.Imaging;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class FileJobViewModel : ObservableObject
+{
+ public SingleJob Job { get; }
+ public VideoInfo? Probe { get; set; }
+ public PreviewData? Preview { get; set; }
+ public ProgressInfo? Progress { get; set; }
+
+ [ObservableProperty]
+ private Bitmap? _thumbnail;
+
+ public string FileName { get; set; }
+
+
+ [ObservableProperty]
+ private string _suggestedAction = "";
+
+ private readonly IThumbnailService _thumbnails;
+ private readonly IFileProbeService _fileProbe;
+
+ public FileJobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe)
+ {
+ Job = job;
+ _thumbnails = thumbnails;
+ _fileProbe = fileProbe;
+
+ FileName = Path.GetFileName(job.InputFile);
+
+ _ = Task.Run( LoadThumbnailAsync );
+ }
+
+ private async Task LoadThumbnailAsync()
+ {
+ Probe = await _fileProbe.ProbeAsync(Job);
+ Thumbnail = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe);
+ }
+}
diff --git a/Splitter-UI/ViewModels/FileListViewModel.cs b/Splitter-UI/ViewModels/FileListViewModel.cs
new file mode 100644
index 0000000..c67a227
--- /dev/null
+++ b/Splitter-UI/ViewModels/FileListViewModel.cs
@@ -0,0 +1,36 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class FileListViewModel : ObservableObject
+{
+ private readonly IFileJobFactory _factory;
+ public ObservableCollection Files { get; } = [];
+
+ [ObservableProperty]
+ private FileJobViewModel? _selected;
+
+ public event Action? SelectedFileChanged;
+
+ public FileListViewModel(IFileJobFactory factory)
+ {
+ _factory = factory;
+ }
+
+ partial void OnSelectedChanged(FileJobViewModel? value)
+ => SelectedFileChanged?.Invoke(value);
+
+ [RelayCommand]
+ private void AddFiles(IEnumerable paths)
+ {
+ foreach (var path in paths)
+ {
+ // Probe + auto-detect + thumbnail
+ var job = new SingleJob { InputFile = path };
+ var vm = _factory.Create(job);
+ Files.Add(vm);
+ }
+ }
+}
diff --git a/Splitter-UI/ViewModels/InspectorPaneViewModel.cs b/Splitter-UI/ViewModels/InspectorPaneViewModel.cs
new file mode 100644
index 0000000..f8270cf
--- /dev/null
+++ b/Splitter-UI/ViewModels/InspectorPaneViewModel.cs
@@ -0,0 +1,25 @@
+using System.Collections.ObjectModel;
+using Avalonia.Controls;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class InspectorPaneViewModel : ObservableObject
+{
+ [ObservableProperty]
+ private FileJobViewModel? _selected;
+
+ public List DetectModes =>
+ [
+ "face", "body", "none"
+ ];
+
+ [RelayCommand]
+ private void ApplyOverrides()
+ {
+ if (Selected is null)
+ return;
+
+ }
+}
diff --git a/Splitter-UI/ViewModels/LogPaneViewModel.cs b/Splitter-UI/ViewModels/LogPaneViewModel.cs
new file mode 100644
index 0000000..e20f396
--- /dev/null
+++ b/Splitter-UI/ViewModels/LogPaneViewModel.cs
@@ -0,0 +1,16 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System.Collections.ObjectModel;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class LogPaneViewModel : ObservableObject
+{
+ public ObservableCollection Logs { get; } = [];
+
+ public void Add(string message)
+ {
+ Logs.Add(message);
+ if (Logs.Count > 5000)
+ Logs.RemoveAt(0);
+ }
+}
diff --git a/Splitter-UI/ViewModels/MainViewModel.cs b/Splitter-UI/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..432f695
--- /dev/null
+++ b/Splitter-UI/ViewModels/MainViewModel.cs
@@ -0,0 +1,36 @@
+using CommunityToolkit.Mvvm.Input;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class MainViewModel : ViewModelBase
+{
+ public FileListViewModel FileList { get; }
+ public PreviewPaneViewModel Preview { get; } = new PreviewPaneViewModel();
+ public InspectorPaneViewModel Inspector { get; } = new InspectorPaneViewModel();
+ public StatusBarViewModel StatusBar { get; } = new StatusBarViewModel();
+ public LogPaneViewModel LogPane { get; } = new LogPaneViewModel();
+
+ public MainViewModel(IFileJobFactory fileJobFactory)
+ {
+ FileList = new FileListViewModel(fileJobFactory);
+ // Wire selection → preview + inspector
+ FileList.SelectedFileChanged += file =>
+ {
+ Preview.Selected = file;
+ Inspector.Selected = file;
+ };
+ }
+
+ [RelayCommand]
+ private void Start()
+ {
+ StatusBar.StatusText = "Processing…";
+ // call IProcessingService here
+ }
+
+ [RelayCommand]
+ private void Stop()
+ {
+ StatusBar.StatusText = "Stopped";
+ }
+}
diff --git a/Splitter-UI/ViewModels/PreviewPaneViewModel.cs b/Splitter-UI/ViewModels/PreviewPaneViewModel.cs
new file mode 100644
index 0000000..464cc5b
--- /dev/null
+++ b/Splitter-UI/ViewModels/PreviewPaneViewModel.cs
@@ -0,0 +1,13 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class PreviewPaneViewModel : ObservableObject
+{
+ [ObservableProperty]
+ private FileJobViewModel? _selected;
+
+ public PreviewPaneViewModel()
+ {
+ }
+}
diff --git a/Splitter-UI/ViewModels/StatusBarViewModel.cs b/Splitter-UI/ViewModels/StatusBarViewModel.cs
new file mode 100644
index 0000000..0a8e3b3
--- /dev/null
+++ b/Splitter-UI/ViewModels/StatusBarViewModel.cs
@@ -0,0 +1,15 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Splitter_UI.ViewModels;
+
+public partial class StatusBarViewModel : ObservableObject
+{
+ [ObservableProperty]
+ private string _statusText = "Ready";
+
+ [ObservableProperty]
+ private double _percent;
+
+ [ObservableProperty]
+ private string _threadInfo = "Threads: 0/0";
+}
diff --git a/Splitter-UI/ViewModels/ViewModelBase.cs b/Splitter-UI/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..be3ad20
--- /dev/null
+++ b/Splitter-UI/ViewModels/ViewModelBase.cs
@@ -0,0 +1,7 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Splitter_UI.ViewModels;
+
+public abstract class ViewModelBase : ObservableObject
+{
+}
diff --git a/Splitter-UI/Views/FileListView.axaml b/Splitter-UI/Views/FileListView.axaml
new file mode 100644
index 0000000..59e9c0e
--- /dev/null
+++ b/Splitter-UI/Views/FileListView.axaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/FileListView.axaml.cs b/Splitter-UI/Views/FileListView.axaml.cs
new file mode 100644
index 0000000..8b12827
--- /dev/null
+++ b/Splitter-UI/Views/FileListView.axaml.cs
@@ -0,0 +1,72 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Platform.Storage;
+using Splitter_UI.ViewModels;
+
+namespace Splitter_UI.Views;
+
+public partial class FileListView : UserControl
+{
+ public static readonly StyledProperty IsDragActiveProperty =
+ AvaloniaProperty.Register(nameof(IsDragActive));
+
+ public bool IsDragActive
+ {
+ get => GetValue(IsDragActiveProperty);
+ set => SetValue(IsDragActiveProperty, value);
+ }
+
+ public FileListView()
+ {
+ InitializeComponent();
+ }
+
+ private void OnDragEnter(object? sender, DragEventArgs e)
+ {
+ IsDragActive = true;
+ }
+
+ private void OnDragLeave(object? sender, DragEventArgs e)
+ {
+ IsDragActive = false;
+ }
+
+ private void OnDragOver(object? sender, DragEventArgs e)
+ {
+ // Avalonia 12:
+ // e.Data is IDataObject, but it has NO strongly typed formats.
+ if (e.DataTransfer.Contains(DataFormat.File))
+ e.DragEffects = DragDropEffects.Copy;
+ else
+ e.DragEffects = DragDropEffects.None;
+
+ e.Handled = true;
+ }
+
+ private async void OnDrop(object? sender, DragEventArgs e)
+ {
+ IsDragActive = false;
+
+ if (DataContext is not FileListViewModel vm)
+ return;
+
+ if (!e.DataTransfer.Contains(DataFormat.File))
+ return;
+
+ // Avalonia 12:
+ // This is the ONLY correct way to get dropped files.
+ var items = e.DataTransfer.TryGetFiles();
+ if (items is null)
+ return;
+
+ var paths = items
+ .OfType()
+ .Select(f => f.Path.LocalPath)
+ .ToList();
+
+ if (paths.Count > 0)
+ vm.AddFilesCommand.Execute(paths);
+ }
+}
diff --git a/Splitter-UI/Views/InspectorPane.axaml b/Splitter-UI/Views/InspectorPane.axaml
new file mode 100644
index 0000000..645ef85
--- /dev/null
+++ b/Splitter-UI/Views/InspectorPane.axaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/InspectorPane.axaml.cs b/Splitter-UI/Views/InspectorPane.axaml.cs
new file mode 100644
index 0000000..68f23c2
--- /dev/null
+++ b/Splitter-UI/Views/InspectorPane.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Splitter_UI.Views;
+
+public partial class InspectorPane : UserControl
+{
+ public InspectorPane()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Splitter-UI/Views/LogPane.axaml b/Splitter-UI/Views/LogPane.axaml
new file mode 100644
index 0000000..b45ce36
--- /dev/null
+++ b/Splitter-UI/Views/LogPane.axaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/LogPane.axaml.cs b/Splitter-UI/Views/LogPane.axaml.cs
new file mode 100644
index 0000000..e65040f
--- /dev/null
+++ b/Splitter-UI/Views/LogPane.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Splitter_UI.Views;
+
+public partial class LogPane : UserControl
+{
+ public LogPane()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Splitter-UI/Views/MainWindow.axaml b/Splitter-UI/Views/MainWindow.axaml
new file mode 100644
index 0000000..f27f108
--- /dev/null
+++ b/Splitter-UI/Views/MainWindow.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/MainWindow.axaml.cs b/Splitter-UI/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..b6d5500
--- /dev/null
+++ b/Splitter-UI/Views/MainWindow.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace Splitter_UI.Views;
+
+public partial class MainWindow : Window
+{
+ public MainViewModel Data { get; } = null!; // set by DI
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Splitter-UI/Views/PreviewPane.axaml b/Splitter-UI/Views/PreviewPane.axaml
new file mode 100644
index 0000000..15d7c76
--- /dev/null
+++ b/Splitter-UI/Views/PreviewPane.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/PreviewPane.axaml.cs b/Splitter-UI/Views/PreviewPane.axaml.cs
new file mode 100644
index 0000000..8852e0b
--- /dev/null
+++ b/Splitter-UI/Views/PreviewPane.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Splitter_UI.Views;
+
+public partial class PreviewPane : UserControl
+{
+ public PreviewPane()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Splitter-UI/Views/StatusBarView.axaml b/Splitter-UI/Views/StatusBarView.axaml
new file mode 100644
index 0000000..1d87d54
--- /dev/null
+++ b/Splitter-UI/Views/StatusBarView.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Splitter-UI/Views/StatusBarView.axaml.cs b/Splitter-UI/Views/StatusBarView.axaml.cs
new file mode 100644
index 0000000..6dddce9
--- /dev/null
+++ b/Splitter-UI/Views/StatusBarView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Splitter_UI.Views;
+
+public partial class StatusBarView : UserControl
+{
+ public StatusBarView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Splitter-UI/app.manifest b/Splitter-UI/app.manifest
new file mode 100644
index 0000000..521bcfb
--- /dev/null
+++ b/Splitter-UI/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/splitter.slnx b/splitter.slnx
index a4535bb..ef93f28 100644
--- a/splitter.slnx
+++ b/splitter.slnx
@@ -6,4 +6,5 @@
+