Skip to content

Commit

Permalink
add ui scaffold
Browse files Browse the repository at this point in the history
  • Loading branch information
diogotr7 committed Oct 26, 2024
1 parent 097264b commit b89d5be
Show file tree
Hide file tree
Showing 33 changed files with 919 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/StarBreaker.Cli/P4kCommands/ExtractP4kCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ExtractP4kCommand : ICommand

public ValueTask ExecuteAsync(IConsole console)
{
var p4k = new P4kFile(P4kFile);
var p4k = P4k.P4kFile.FromFile(P4kFile);

console.Output.WriteLine("DataForge loaded.");
console.Output.WriteLine("Exporting...");
Expand Down
25 changes: 19 additions & 6 deletions src/StarBreaker.P4k/P4kFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ public sealed class P4kFile

public ZipEntry[] Entries => _entries;

public P4kFile(string filePath)
private P4kFile(string path, ZipEntry[] entries)
{
P4KPath = filePath;
using var reader = new BinaryReader(new FileStream(P4KPath, FileMode.Open, FileAccess.Read, FileShare.Read, 1024 * 1024), Encoding.UTF8, false);
P4KPath = path;
_entries = entries;
}

public static P4kFile FromFile(string filePath, IProgress<double>? progress = null)
{
progress?.Report(0);
using var reader = new BinaryReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 1024 * 1024), Encoding.UTF8, false);

var eocdLocation = reader.Locate(EOCDRecord.Magic);
reader.BaseStream.Seek(eocdLocation, SeekOrigin.Begin);
Expand All @@ -42,8 +48,8 @@ public P4kFile(string filePath)
if (eocd64.Signature != BitConverter.ToUInt32(EOCD64Record.Magic))
throw new Exception("Invalid zip64 end of central directory locator");

_entries = new ZipEntry[eocd64.EntriesOnDisk];

var _entries = new ZipEntry[eocd64.EntriesOnDisk];
var reportInterval = (int)Math.Max(eocd64.TotalEntries / 50, 1);
reader.BaseStream.Seek((long)eocd64.CentralDirectoryOffset, SeekOrigin.Begin);

for (var i = 0; i < (int)eocd64.TotalEntries; i++)
Expand Down Expand Up @@ -115,12 +121,19 @@ public P4kFile(string filePath)
header.LastModifiedTime,
header.LastModifiedDate
);

if (i % reportInterval == 0)
progress?.Report(i / (double)eocd64.TotalEntries);
}
finally
{
ArrayPool<byte>.Shared.Return(rent);
}
}

progress?.Report(1);

return new P4kFile(filePath, _entries);
}

public void Extract(string outputDir, string? filter = null, IProgress<double>? progress = null)
Expand All @@ -132,7 +145,7 @@ public void Extract(string outputDir, string? filter = null, IProgress<double>?
var processedEntries = 0;

progress?.Report(0);

//TODO: Preprocessing step:
// 1. start with the list of total files
// 2. run the following according to the filter:
Expand Down
2 changes: 1 addition & 1 deletion src/StarBreaker.Sandbox/TimeP4kExtract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static class TimeP4kExtract
public static void Run()
{
var sw1 = Stopwatch.StartNew();
var p4kFile = new P4kFile(p4k);
var p4kFile = P4kFile.FromFile(p4k);
sw1.Stop();

Console.WriteLine($"Took {sw1.ElapsedMilliseconds}ms to load {p4kFile.Entries.Length} entries");
Expand Down
2 changes: 1 addition & 1 deletion src/StarBreaker.Sandbox/TimeZipNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class TimeZipNode

public static void Run()
{
var p4kFile = new P4kFile(p4k);
var p4kFile = P4kFile.FromFile(p4k);

var times = new List<long>();
for (var i = 0; i < 8; i++)
Expand Down
1 change: 1 addition & 0 deletions src/StarBreaker.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
<Project Path="StarBreaker.Protobuf\StarBreaker.Protobuf.csproj" />
<Project Path="StarBreaker.Sandbox\StarBreaker.Sandbox.csproj" />
<Project Path="StarBreaker.Tests\StarBreaker.Tests.csproj" />
<Project Path="StarBreaker\StarBreaker.csproj" Type="Classic C#" />
</Solution>
45 changes: 45 additions & 0 deletions src/StarBreaker/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:starBreaker="clr-namespace:StarBreaker"
x:Class="StarBreaker.App"
RequestedThemeVariant="Default">
<Application.DataTemplates>
<starBreaker:ViewLocator />
</Application.DataTemplates>

<Application.Styles>
<sty:FluentAvaloniaTheme UseSystemFontOnWindows="True" />
<StyleInclude Source="avares://Avalonia.Controls.TreeDataGrid/Themes/Fluent.axaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
<StyleInclude Source="avares://AvaloniaHex/Themes/Simple/AvaloniaHex.axaml"/>
</Application.Styles>

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="TreeDataGridGridLinesBrush" Color="Black" Opacity="0.1" />
<SolidColorBrush x:Key="TreeDataGridHeaderBackgroundPointerOverBrush" Color="Black" Opacity="0.1" />
<SolidColorBrush x:Key="TreeDataGridHeaderBackgroundPressedBrush" Color="Black" Opacity="0.6" />
<SolidColorBrush x:Key="TreeDataGridHeaderBorderBrushPointerOverBrush" Color="Transparent" />
<SolidColorBrush x:Key="TreeDataGridHeaderBorderBrushPressedBrush" Color="Transparent" />
<SolidColorBrush x:Key="TreeDataGridHeaderForegroundPointerOverBrush" Color="Black" />
<SolidColorBrush x:Key="TreeDataGridHeaderForegroundPressedBrush" Color="Black" />
<SolidColorBrush x:Key="TreeDataGridSelectedCellBackgroundBrush" Color="Black" Opacity="0.4" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="TreeDataGridGridLinesBrush" Color="White" Opacity="0.1" />
<SolidColorBrush x:Key="TreeDataGridHeaderBackgroundPointerOverBrush" Color="White" Opacity="0.1" />
<SolidColorBrush x:Key="TreeDataGridHeaderBackgroundPressedBrush" Color="White" Opacity="0.6" />
<SolidColorBrush x:Key="TreeDataGridHeaderBorderBrushPointerOverBrush" Color="Transparent" />
<SolidColorBrush x:Key="TreeDataGridHeaderBorderBrushPressedBrush" Color="Transparent" />
<SolidColorBrush x:Key="TreeDataGridHeaderForegroundPointerOverBrush" Color="White" />
<SolidColorBrush x:Key="TreeDataGridHeaderForegroundPressedBrush" Color="White" />
<SolidColorBrush x:Key="TreeDataGridSelectedCellBackgroundBrush" Color="White" Opacity="0.4" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
84 changes: 84 additions & 0 deletions src/StarBreaker/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StarBreaker.Extensions;
using StarBreaker.Screens;
using StarBreaker.Services;

namespace StarBreaker;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);

if (Design.IsDesignMode)
{
RequestedThemeVariant = ThemeVariant.Dark;
}
}

private SplashWindow? _splashWindow;
private MainWindow? _mainWindow;

public override void OnFrameworkInitializationCompleted()
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
#pragma warning disable IL2026
BindingPlugins.DataValidators.RemoveAt(0);
#pragma warning restore IL2026

var collection = new ServiceCollection();

collection.RegisterServices();
ViewLocator.RegisterViews();

Services = collection.BuildServiceProvider();

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var splashVm = Services.GetRequiredService<SplashWindowViewModel>();
_splashWindow = new SplashWindow { DataContext = splashVm };

splashVm.P4kLoaded += SwapWindows;

desktop.MainWindow = _splashWindow;
_splashWindow.Show();
}

base.OnFrameworkInitializationCompleted();
}

private void SwapWindows(object? sender, EventArgs e)
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;

var mainVm = Services.GetRequiredService<MainWindowViewModel>();
_mainWindow = new MainWindow { DataContext = mainVm };

//do not change the order of these
_mainWindow.Show();
_splashWindow!.Close();

desktop.MainWindow = _mainWindow;
}

public new static App Current => Application.Current as App ?? throw new InvalidOperationException("App.Current is null");

public static IStorageProvider StorageProvider => (Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow?.StorageProvider ??
throw new InvalidOperationException("StorageProvider is null");

/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; private set; } = null!;
}
Binary file added src/StarBreaker/Assets/avalonia-logo.ico
Binary file not shown.
24 changes: 24 additions & 0 deletions src/StarBreaker/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Avalonia.Platform.Storage;

namespace StarBreaker;

public static class Constants
{
public const string DefaultStarCitizenFolder = @"C:\Program Files\Roberts Space Industries\StarCitizen\";
public const string DataP4k = "Data.p4k";

public static FilePickerOpenOptions GetP4kFilter(IStorageFolder? defaultPath) => new()
{
FileTypeFilter =
[
new FilePickerFileType("P4k File")
{
Patterns = ["*.p4k"],
}
],
AllowMultiple = false,
Title = "Select a P4k file",
SuggestedFileName = DataP4k,
SuggestedStartLocation = defaultPath
};
}
13 changes: 13 additions & 0 deletions src/StarBreaker/DesignData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StarBreaker.Screens;
using StarBreaker.Services;

namespace StarBreaker;

public static class DesignData
{
public static SplashWindowViewModel SplashWindowViewModel { get; } = App.Current.Services.GetRequiredService<SplashWindowViewModel>();
public static MainWindowViewModel MainWindowViewModel { get; } = App.Current.Services.GetRequiredService<MainWindowViewModel>();
public static HomeViewModel HomeViewModel { get; } = App.Current.Services.GetRequiredService<HomeViewModel>();
}
14 changes: 14 additions & 0 deletions src/StarBreaker/Extensions/AppWindowExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Avalonia.Controls;
using Avalonia.Media;
using FluentAvalonia.UI.Windowing;

namespace StarBreaker.Extensions;

public static class AppWindowExtensions
{
public static void EnableMicaTransparency(this AppWindow window)
{
window.TransparencyLevelHint = [WindowTransparencyLevel.Mica];
window.Background = new SolidColorBrush(new Color(0, 0,0,0));
}
}
19 changes: 19 additions & 0 deletions src/StarBreaker/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StarBreaker.Screens;
using StarBreaker.Services;

namespace StarBreaker.Extensions;

public static class ServiceCollectionExtensions
{
public static void RegisterServices(this ServiceCollection services)
{
services.AddLogging(b => { b.AddSimpleConsole(options => { options.SingleLine = true; }); });
services.AddTransient<MainWindowViewModel>();
services.AddTransient<SplashWindowViewModel>();
services.AddTransient<HomeViewModel>();
services.AddTransient<AboutViewModel>();
services.AddSingleton<IP4kService, P4kService>();
}
}
86 changes: 86 additions & 0 deletions src/StarBreaker/Models/ZipNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using Humanizer;
using StarBreaker.P4k;
using StarBreaker.Screens;

namespace StarBreaker.Models;

public partial class ZipNode : ViewModelBase
{
private static readonly Dictionary<string, ZipNode> EmptyList = new();

public ZipEntry? ZipEntry { get; }
public string Name { get; }
public Dictionary<string, ZipNode> Children { get; }

/// <summary>
/// Constructor for creating a file node
/// </summary>
/// <param name="entry"></param>
public ZipNode(ZipEntry entry)
{
ZipEntry = entry;
Name = entry.Name.Split('\\').Last();
Children = EmptyList;
}

/// <summary>
/// Constructor for creating a directory node
/// </summary>
/// <param name="name"></param>
public ZipNode(string name)
{
Name = name;
Children = new Dictionary<string, ZipNode>();
}

public string SizeUi => ((long?)ZipEntry?.UncompressedSize)?.Bytes().ToString() ?? "";
public string DateModifiedUi => ZipEntry?.LastModified.ToString("yyyy-mm-dd", CultureInfo.InvariantCulture) ?? "";
public string CompressionMethodUi => ZipEntry?.CompressionMethod.ToString() ?? "";
public string EncryptedUi => ZipEntry?.IsCrypted.ToString() ?? "";

[ObservableProperty]
private bool _isChecked;

public ZipNode(ZipEntry[] zipEntries, IProgress<double>? progress = null)
{
progress?.Report(0);
var report = Math.Max(1, zipEntries.Length / 100);
Name = "";
var root = new ZipNode("");
int count = 0;
foreach (var zipEntry in zipEntries)
{
var parts = zipEntry.Name.Split('\\');
var current = root;

for (var index = 0; index < parts.Length; index++)
{
var part = parts[index];

// If this is the last part, we're at the file
if (index == parts.Length - 1)
{
current.Children[part] = new ZipNode(zipEntry);
break;
}

if (!current.Children.TryGetValue(part, out var value))
{
value = new ZipNode(part);
current.Children[part] = value;
}

current = value;
}
count++;
if (progress != null && count % report == 0)
{
progress.Report(count / (double)zipEntries.Count());
}
}

Children = root.Children;
}
}
Loading

0 comments on commit b89d5be

Please sign in to comment.