More project base

This commit is contained in:
2022-04-01 20:38:43 +02:00
parent d0edf85e98
commit 8e09bf41bb
76 changed files with 3589 additions and 78 deletions

View File

@@ -0,0 +1,215 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileTime.GuiApp"
x:Class="FileTime.GuiApp.App"
xmlns:converters="using:FileTime.GuiApp.Converters">
<Application.Resources>
<ResourceDictionary>
<Color x:Key="AppBackgroundColor">#E7073642</Color>
<Color x:Key="ContainerBackgroundColor">#083e4c</Color>
<Color x:Key="TransparentContainerBackgroundColor">#D0083e4c</Color>
<Color x:Key="BarelyTransparentBackgroundColor">#80083e4c</Color>
<Color x:Key="ItemBackgroundColor">#00000000</Color>
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</Color>
<Color x:Key="MarkedItemBackgroundColor">#00000000</Color>
<Color x:Key="MarkedAlternativeItemBackgroundColor">#10000000</Color>
<Color x:Key="MarkedSelectedItemBackgroundColor">#b58900</Color>
<Color x:Key="ForegroundColor">#93a1a1</Color>
<Color x:Key="AccentColor">#268bd2</Color>
<Color x:Key="AccentComplementColor">#fdf6e3</Color>
<Color x:Key="LightForegroundColor">#7793a1a1</Color>
<Color x:Key="AlternativeItemForegroundColor">#93a1a1</Color>
<Color x:Key="SelectedItemForegroundColor">#073642</Color>
<Color x:Key="MarkedItemForegroundColor">#b58900</Color>
<Color x:Key="MarkedAlternativeItemForegroundColor">#b58900</Color>
<Color x:Key="MarkedSelectedItemForegroundColor">#002b36</Color>
<Color x:Key="ErrorColor">#dc322f</Color>
<LinearGradientBrush x:Key="ContentSeparatorBrush">
<GradientStop Offset="0" Color="#0093a1a1" />
<GradientStop Offset="0.5" Color="#93a1a1" />
<GradientStop Offset="1" Color="#0093a1a1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ContainerGradientBackgroundBrush">
<GradientStop Offset="0" Color="#00b58900" />
<GradientStop Offset="0.4" Color="#CCb58900" />
<GradientStop Offset="0.6" Color="#CCb58900" />
<GradientStop Offset="1" Color="#00b58900" />
</LinearGradientBrush>
<SolidColorBrush
x:Key="AppBackgroundBrush"
Color="{DynamicResource AppBackgroundColor}" />
<SolidColorBrush
x:Key="ContainerBackgroundBrush"
Color="{DynamicResource ContainerBackgroundColor}" />
<SolidColorBrush
x:Key="TransparentContainerBackgroundBrush"
Color="{DynamicResource TransparentContainerBackgroundColor}" />
<SolidColorBrush
x:Key="ItemBackgroundBrush"
Color="{DynamicResource ItemBackgroundColor}" />
<SolidColorBrush
x:Key="AlternativeItemBackgroundBrush"
Color="{DynamicResource AlternativeItemBackgroundColor}" />
<SolidColorBrush
x:Key="SelectedItemBackgroundBrush"
Color="{DynamicResource SelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedItemBackgroundBrush"
Color="{DynamicResource MarkedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemBackgroundBrush"
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemBackgroundBrush"
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
<SolidColorBrush
x:Key="ForegroundBrush"
Color="{DynamicResource ForegroundColor}" />
<SolidColorBrush
x:Key="AccentBrush"
Color="{DynamicResource AccentColor}" />
<SolidColorBrush
x:Key="AccentComplementBrush"
Color="{DynamicResource AccentComplementColor}" />
<SolidColorBrush
x:Key="LightForegroundBrush"
Color="{DynamicResource LightForegroundColor}" />
<SolidColorBrush
x:Key="AlternativeItemForegroundBrush"
Color="{DynamicResource AlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="SelectedItemForegroundBrush"
Color="{DynamicResource SelectedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedItemForegroundBrush"
Color="{DynamicResource MarkedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemForegroundBrush"
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemForegroundBrush"
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
<SolidColorBrush
x:Key="ErrorBrush"
Color="{DynamicResource ErrorColor}" />
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{DynamicResource SelectedItemBackgroundColor}" />
<converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToForegroundConverter"
DefaultBrush="{StaticResource ForegroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
<converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToBackgroundConverter"
DefaultBrush="{StaticResource ItemBackgroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsAttributeTypeConverter"/>
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsNotAttributeTypeConverter" Invert="true"/>
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
<converters:FormatSizeConverter x:Key="FormatSizeConverter"/>
<converters:DateTimeConverter x:Key="DateTimeConverter"/>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme Mode="Dark"/>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
<Setter Property="FontSize" Value="16"/>
</Style>
<Style Selector="TextBlock.SmallText">
<Setter Property="FontSize" Value="12"/>
</Style>
<Style Selector="TextBlock.ExtraSmallText">
<Setter Property="FontSize" Value="11"/>
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
<Setter Property="Background" Value="{DynamicResource ContainerBackgroundBrush}"/>
</Style>
<Style Selector="ListBox.ContentListView">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Style Selector="ListBox.ContentListView > ListBoxItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<!--Setter Property="ContextMenu">
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
</Setter-->
</Style>
<Style Selector="Grid.SidebarContainerPresenter">
<Setter Property="Background" Value="#01000000"/>
</Style>
<Style Selector="Grid.SidebarContainerPresenter:pointerover">
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
</Style>
<Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
</Style>
<Style Selector="ListBox.RadioButtonListBox">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="ListBox.RadioButtonListBox ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Foreground="{DynamicResource ForegroundBrush}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Image.LoadingAnimation">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="INFINITE" Easing="QuadraticEaseInOut">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="45"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="405"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Application.Styles>
</Application>

View File

@@ -1,13 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using FileTime.App.DependencyInjection;
using FileTime.GuiApp.ViewModels;
using FileTime.GuiApp.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp
{
public partial class App : Application
{
static App()
{
DI.ServiceProvider ??= DependencyInjection
.RegisterDefaultServices()
.AddConfiguration()
.RegisterLogging()
.AddViewModels()
.BuildServiceProvider()
.InitSerilog();
var logger = DI.ServiceProvider.GetRequiredService<ILogger<App>>();
logger.LogInformation("App initialization completed.");
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -19,7 +35,7 @@ namespace FileTime.GuiApp
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
DataContext = new MainWindowLoadingViewModel(),
};
}

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>filetime.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="filetime.ico" />
</ItemGroup>
<ItemGroup>
<!--This helps with theme dll-s trimming.
If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
<TrimmableAssembly Include="Avalonia.Themes.Fluent" />
<TrimmableAssembly Include="Avalonia.Themes.Default" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
<ProjectReference Include="..\FileTime.GuiApp.CustomImpl\FileTime.GuiApp.CustomImpl.csproj" />
<ProjectReference Include="..\FileTime.GuiApp\FileTime.GuiApp.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Content Include="appsettings.Development.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using Avalonia;
using FileTime.GuiApp;
namespace FileTime.GuiApp
{
public static class Program
{
public static string AppDataRoot { get; }
public static string EnvironmentName { get; }
static Program()
{
#if DEBUG
EnvironmentName = "Development";
AppDataRoot = Path.Combine(Environment.CurrentDirectory, "appdata");
#else
EnvironmentName = "Release";
var possibleDataRootsPaths = new List<string>()
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FileTime"),
Path.Combine(Assembly.GetEntryAssembly()?.Location ?? ".", "fallbackDataRoot")
};
string? appDataRoot = null;
foreach (var possibleAppDataRoot in possibleDataRootsPaths)
{
try
{
var appDataRootDirectory = new DirectoryInfo(possibleAppDataRoot);
if (!appDataRootDirectory.Exists) appDataRootDirectory.Create();
//TODO write test
appDataRoot = possibleAppDataRoot;
break;
}
catch { }
}
if (appDataRoot == null) throw new UnauthorizedAccessException();
AppDataRoot = appDataRoot;
#endif
}
// 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()
.LogToTrace();
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.IO;
using FileTime.App.Core;
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Services;
using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Logging;
using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Configuration;
namespace FileTime.GuiApp
{
public static class Startup
{
internal static IServiceCollection AddViewModels(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<MainWindowViewModel>()
.AddSingleton<AppState>()
.AddSingleton<IAppState, AppState>(s => s.GetRequiredService<AppState>())
//TODO: move??
.AddTransient<ITab, Tab>()
.AddTransient<ITabViewModel, TabViewModel>()
.AddTransient<IContainerViewModel, ContainerViewModel>()
.AddTransient<IElementViewModel, ElementViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>();
}
internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection)
{
return serviceCollection.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true)
);
}
internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(MainConfiguration.Configuration)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Program.EnvironmentName}.json", true, true)
.Build();
return serviceCollection
.Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName))
.Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName))
.AddSingleton<IConfiguration>(configuration);
}
internal static IServiceProvider InitSerilog(this IServiceProvider serviceProvider)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(serviceProvider.GetService<IConfiguration>())
.Enrich.FromLogContext()
.WriteTo.File(
Path.Combine(Program.AppDataRoot, "logs", "appLog.log"),
fileSizeLimitBytes: 10 * 1024 * 1024,
rollOnFileSizeLimit: true,
rollingInterval: RollingInterval.Day)
.WriteTo.MessageBoxSink(serviceProvider)
.CreateLogger();
return serviceProvider;
}
internal static LoggerConfiguration MessageBoxSink(
this LoggerSinkConfiguration loggerConfiguration,
IServiceProvider serviceProvider)
{
return loggerConfiguration.Sink(serviceProvider.GetService<ToastMessageSink>());
}
}
}

View File

@@ -0,0 +1,11 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
using FileTime.App.Core;
namespace FileTime.GuiApp.ViewModels
{
public class AppState : AppStateBase { }
}

View File

@@ -1,11 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileTime.GuiApp"
x:Class="FileTime.GuiApp.App">
<Application.DataTemplates>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using FileTime.App.Core.Command;
namespace FileTime.GuiApp.Configuration
{
public class CommandBindingConfiguration
{
public List<KeyConfig> Keys { get; set; } = new List<KeyConfig>();
public Commands Command { get; set; } = Commands.None;
public string KeysDisplayText => GetKeysDisplayText();
public CommandBindingConfiguration() { }
public CommandBindingConfiguration(Commands command, IEnumerable<KeyConfig> keys)
{
Keys = new List<KeyConfig>(keys);
Command = command;
}
public CommandBindingConfiguration(Commands command, KeyConfig key)
{
Keys = new List<KeyConfig>() { key };
Command = command;
}
public CommandBindingConfiguration(Commands command, IEnumerable<Key> keys)
{
Keys = keys.Select(k => new KeyConfig(k)).ToList();
Command = command;
}
public CommandBindingConfiguration(Commands command, Key key)
{
Keys = new List<KeyConfig>() { new KeyConfig(key) };
Command = command;
}
public string GetKeysDisplayText()
{
var s = "";
foreach (var k in Keys)
{
var keyString = k.Key.ToString();
if (keyString.Length == 1)
{
s += AddKeyWithCtrlOrAlt(k, s, (_, _, _) => k.Shift ? keyString.ToUpper() : keyString.ToLower());
}
else
{
s += AddKeyWithCtrlOrAlt(k, s, AddSpecialKey);
}
}
return s;
}
private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor)
{
var s = "";
bool ctrlOrAlt = key.Ctrl || key.Alt;
if (ctrlOrAlt && currentText.Length > 0 && currentText.Last() != ' ') s += " ";
if (key.Ctrl) s += "CTRL+";
if (key.Alt) s += "ALT+";
s += keyProcessor(key, currentText, ctrlOrAlt);
if (ctrlOrAlt) s += " ";
return s;
}
private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt)
{
var s = "";
if (currentText.Length > 0 && currentText.Last() != ' ' && !wasCtrlOrAlt) s += " ";
s += key.Key.ToString();
if (!wasCtrlOrAlt) s += " ";
return s;
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public class KeyBindingConfiguration
{
public bool UseDefaultBindings { get; set; } = true;
public List<CommandBindingConfiguration> DefaultKeyBindings { get; set; } = new();
public List<CommandBindingConfiguration> KeyBindings { get; set; } = new();
}
}

View File

@@ -0,0 +1,22 @@
using Avalonia.Input;
namespace FileTime.GuiApp.Configuration
{
public class KeyConfig
{
public Key Key { get; set; }
public bool Shift { get; set; }
public bool Alt { get; set; }
public bool Ctrl { get; set; }
public KeyConfig() { }
public KeyConfig(Key key, bool shift = false, bool alt = false, bool ctrl = false)
{
Key = key;
Shift = shift;
Alt = alt;
Ctrl = ctrl;
}
}
}

View File

@@ -0,0 +1,128 @@
using Avalonia.Input;
using FileTime.App.Core.Command;
using System;
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public static class MainConfiguration
{
private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings);
public static Dictionary<string, string> Configuration { get; }
static MainConfiguration()
{
Configuration = new();
PopulateDefaultEditorPrograms(Configuration);
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, SectionNames.KeybindingSectionName + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
}
private static void PopulateDefaultKeyBindings(Dictionary<string, string> configuration, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
{
for (var i = 0; i < commandBindingConfigs.Count; i++)
{
var baseKey = basePath + $":[{i}]:";
var commandBindingConfig = commandBindingConfigs[i];
configuration.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
for (var j = 0; j < commandBindingConfig.Keys.Count; j++)
{
var key = commandBindingConfig.Keys[j];
var keyBaseKey = baseKey + $"keys:[{j}]:";
configuration.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
}
}
}
private static List<CommandBindingConfiguration> InitDefaultKeyBindings()
{
return new List<CommandBindingConfiguration>()
{
new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)),
new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }),
new CommandBindingConfiguration(Commands.CloseTab, Key.Q),
new CommandBindingConfiguration(Commands.Compress, new[] { Key.Y, Key.C }),
new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),
new CommandBindingConfiguration(Commands.CreateContainer, Key.F7),
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),
new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }),
new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }),
new CommandBindingConfiguration(Commands.Edit, new KeyConfig(Key.F4)),
new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)),
new CommandBindingConfiguration(Commands.FindByName, new[] { Key.F, Key.N }),
new CommandBindingConfiguration(Commands.FindByNameRegex, new[] { Key.F, Key.R }),
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.L, ctrl: true)),
new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }),
new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }),
new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }),
new CommandBindingConfiguration(Commands.HardDelete, new[] { new KeyConfig(Key.D,shift: true), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.Mark, Key.Space),
new CommandBindingConfiguration(Commands.MoveToLast, new KeyConfig(Key.G, shift: true)),
new CommandBindingConfiguration(Commands.MoveToFirst, new[] { Key.G, Key.G }),
new CommandBindingConfiguration(Commands.NextTimelineBlock, Key.L ),
new CommandBindingConfiguration(Commands.NextTimelineCommand, Key.J ),
new CommandBindingConfiguration(Commands.OpenInFileBrowser, new[] { Key.O, Key.E }),
new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }),
new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }),
new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }),
new CommandBindingConfiguration(Commands.PinFavorite, new[] { Key.F, Key.P }),
new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ),
new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ),
new CommandBindingConfiguration(Commands.Refresh, Key.R),
new CommandBindingConfiguration(Commands.Rename, Key.F2),
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
new CommandBindingConfiguration(Commands.ScanContainerSize, new[] { Key.C, Key.S }),
new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1),
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
new CommandBindingConfiguration(Commands.SwitchToTab1, Key.D1),
new CommandBindingConfiguration(Commands.SwitchToTab2, Key.D2),
new CommandBindingConfiguration(Commands.SwitchToTab3, Key.D3),
new CommandBindingConfiguration(Commands.SwitchToTab4, Key.D4),
new CommandBindingConfiguration(Commands.SwitchToTab5, Key.D5),
new CommandBindingConfiguration(Commands.SwitchToTab6, Key.D6),
new CommandBindingConfiguration(Commands.SwitchToTab7, Key.D7),
new CommandBindingConfiguration(Commands.SwitchToTab8, Key.D8),
new CommandBindingConfiguration(Commands.TimelinePause, new[] { Key.T, Key.P }),
new CommandBindingConfiguration(Commands.TimelineRefresh, new[] { Key.T, Key.R }),
new CommandBindingConfiguration(Commands.TimelineStart, new[] { Key.T, Key.S }),
new CommandBindingConfiguration(Commands.ToggleAdvancedIcons, new[] { Key.Z, Key.I }),
new CommandBindingConfiguration(Commands.GoUp, Key.Left),
new CommandBindingConfiguration(Commands.Open, Key.Right),
new CommandBindingConfiguration(Commands.OpenOrRun, Key.Enter),
new CommandBindingConfiguration(Commands.MoveCursorUp, Key.Up),
new CommandBindingConfiguration(Commands.MoveCursorDown, Key.Down),
new CommandBindingConfiguration(Commands.MoveCursorUpPage, Key.PageUp),
new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown),
};
}
private static void PopulateDefaultEditorPrograms(Dictionary<string, string> configuration)
{
var editorPrograms = new List<ProgramConfiguration>()
{
new ProgramConfiguration(@"c:\Program Files\Notepad++\notepad++.exe"),
new ProgramConfiguration("notepad.exe"),
};
for (var i = 0; i < editorPrograms.Count; i++)
{
if (editorPrograms[i].Path is not string path) continue;
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Path)}", path);
if (editorPrograms[i].Arguments is string arguments)
{
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Arguments)}", arguments);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace FileTime.GuiApp.Configuration
{
public class ProgramConfiguration
{
public string? Path { get; set; }
public string? Arguments { get; set; }
public ProgramConfiguration() { }
public ProgramConfiguration(string? path, string? arguments = null)
{
Path = path;
Arguments = arguments;
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public class ProgramsConfiguration
{
public List<ProgramConfiguration> DefaultEditorPrograms { get; set; } = new();
public List<ProgramConfiguration> EditorPrograms { get; set; } = new();
}
}

View File

@@ -0,0 +1,8 @@
namespace FileTime.GuiApp.Configuration
{
public static class SectionNames
{
public const string KeybindingSectionName = "KeyBindings";
public const string ProgramsSectionName = "Programs";
}
}

View File

@@ -0,0 +1,18 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class DateTimeConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
value is DateTime dateTime && parameter is string parameterS
? dateTime.ToString(parameterS)
: value;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class FormatSizeConverter : IValueConverter
{
private const long OneKiloByte = 1024;
private const long OneMegaByte = OneKiloByte * 1024;
private const long OneGigaByte = OneMegaByte * 1024;
private const long OneTerraByte = OneGigaByte * 1024;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (value, int.TryParse(parameter?.ToString(), out var prec)) switch
{
(long size, true) => ToSizeString(size, prec),
(long size, false) => ToSizeString(size),
(null, _) => "...",
_ => value
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public static string ToSizeString(long fileSize, int precision = 1)
{
var fileSizeD = (decimal)fileSize;
var (size, suffix) = fileSize switch
{
> OneTerraByte => (fileSizeD / OneTerraByte, "T"),
> OneGigaByte => (fileSizeD / OneGigaByte, "G"),
> OneMegaByte => (fileSizeD / OneMegaByte, "M"),
> OneKiloByte => (fileSizeD / OneKiloByte, "K"),
_ => (fileSizeD, "B")
};
var result = string.Format("{0:N" + precision + "}", size).Replace(',', '.');
if (result.Contains('.')) result = result.TrimEnd('0').TrimEnd('.');
return result + " " + suffix;
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Globalization;
using Avalonia.Data.Converters;
using FileTime.App.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.GuiApp.Converters
{
public class GetFileExtensionConverter : IValueConverter
{
private IItemNameConverterService? _itemNameConverterService;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not string fullName) return value;
_itemNameConverterService ??= DI.ServiceProvider.GetRequiredService<IItemNameConverterService>();
return _itemNameConverterService.GetFileExtension(fullName);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FileTime.App.Core.Models.Enums;
namespace FileTime.GuiApp.Converters
{
public class ItemViewModeToBrushConverter : IValueConverter
{
public Brush? DefaultBrush { get; set; }
public Brush? AlternativeBrush { get; set; }
public Brush? SelectedBrush { get; set; }
public Brush? MarkedBrush { get; set; }
public Brush? MarkedSelectedBrush { get; set; }
public Brush? MarkedAlternativeBrush { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is ItemViewMode viewMode)
{
return viewMode switch
{
ItemViewMode.Default => DefaultBrush,
ItemViewMode.Alternative => AlternativeBrush,
ItemViewMode.Selected => SelectedBrush,
ItemViewMode.Marked => MarkedBrush,
ItemViewMode.MarkedSelected => MarkedSelectedBrush,
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
_ => throw new NotImplementedException()
};
}
return value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Globalization;
using Avalonia.Data.Converters;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels;
namespace FileTime.GuiApp.Converters
{
public class ItemViewModelIsAttributeTypeConverter : IValueConverter
{
public bool Invert { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
var attributeType = GetAttributeType(value);
if (parameter == null) return attributeType;
var result = parameter is ItemAttributeType targetAttribute && attributeType == targetAttribute;
if (Invert && parameter is ItemAttributeType) result = !result;
return result;
}
private static ItemAttributeType? GetAttributeType(object? value)
{
return value switch
{
IFileViewModel => ItemAttributeType.File,
IContainerSizeContainerViewModel => ItemAttributeType.SizeContainer,
IElementViewModel => ItemAttributeType.Element,
IContainerViewModel => ItemAttributeType.Container,
_ => null
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FileTime.App.Core.Models;
using FileTime.GuiApp.ViewModels;
namespace FileTime.GuiApp.Converters
{
public class NamePartShrinkerConverter : IMultiValueConverter
{
private const int PixelPerChar = 8;
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
{
var attributeWidth = values[2] is bool b && b ? 340 : 0;
var newNameParts = nameParts;
if (values.Count > 1 && values[1] is double width && width > 0)
{
newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
}
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
}
return null;
}
private static List<ItemNamePart> GetNamePartsForWidth(IList<ItemNamePart> nameParts, double maxWidth)
{
//Best case, we are in the range
var textLength = nameParts.Select(p => p.Text.Length).Sum();
if (textLength * PixelPerChar <= maxWidth)
{
return nameParts.ToList();
}
//Trying at least with the special parts
var newNameParts = new ItemNamePart?[nameParts.Count];
for (var i = 0; i < nameParts.Count; i++)
{
if (nameParts[i].IsSpecial)
{
newNameParts[i] = nameParts[i];
}
}
return GetNamePartsForWidthOptimistic(nameParts, newNameParts, maxWidth);
}
private static List<ItemNamePart> GetNamePartsForWidthOptimistic(IList<ItemNamePart> nameParts, ItemNamePart?[] newNameParts, double maxWidth)
{
var trimmedIndexes = new List<int>();
for (var i = 0; i < newNameParts.Length; i++)
{
if (newNameParts[i] == null)
{
trimmedIndexes.Add(i);
newNameParts[i] = new ItemNamePart("...");
}
}
var textLength = newNameParts.Select(p => p?.Text.Length ?? 0).Sum();
if (textLength * PixelPerChar > maxWidth)
{
return GetNamePartsForWidthPessimistic(nameParts, maxWidth);
}
foreach (var trimmedIndex in trimmedIndexes)
{
var baseTextLength = newNameParts.Select((p, i) => i == trimmedIndex ? 0 : (p?.Text.Length ?? 0)).Sum();
var proposedText = nameParts[trimmedIndex].Text;
var trimmed = false;
while ((baseTextLength + proposedText.Length + (trimmed ? 3 : 0)) * PixelPerChar > maxWidth)
{
proposedText = proposedText[0..^1];
trimmed = true;
}
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
if (trimmed) break;
}
return newNameParts.OfType<ItemNamePart>().ToList();
}
private static List<ItemNamePart> GetNamePartsForWidthPessimistic(IList<ItemNamePart> nameParts, double maxWidth)
{
var newNameParts = new List<ItemNamePart>(nameParts);
foreach (var namePart in nameParts)
{
var baseTextLength = newNameParts.Select(p => p.Text.Length).Sum();
var proposedText = namePart.Text;
var trimmed = false;
while ((baseTextLength + 3) * PixelPerChar > maxWidth && proposedText != "")
{
proposedText = proposedText[0..^1];
trimmed = true;
}
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
if (trimmed) break;
}
if (newNameParts.Last().IsSpecial)
{
newNameParts.Add(new ItemNamePart("..."));
}
else
{
var last = newNameParts.Last();
last.Text += "...";
}
return newNameParts;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp
{
public static class DI
{
public static IServiceProvider ServiceProvider { get; set; } = null!;
}
}

View File

@@ -1,36 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>Assets\filetime.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\filetime.ico" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\filetime.ico" />
<AvaloniaXaml Update="Views\MainWindow.axaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaXaml>
</ItemGroup>
<ItemGroup>
<!--This helps with theme dll-s trimming.
If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
<TrimmableAssembly Include="Avalonia.Themes.Fluent" />
<TrimmableAssembly Include="Avalonia.Themes.Default" />
<Compile Update="Views\MainWindow.axaml.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MvvmGen" Version="1.1.5" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using Serilog.Core;
using Serilog.Events;
namespace FileTime.GuiApp.Logging
{
public class ToastMessageSink : ILogEventSink
{
//private readonly IDialogService dialogService;
public ToastMessageSink(/*IDialogService dialogService*/)
{
//this.dialogService = dialogService;
}
public void Emit(LogEvent logEvent)
{
/*if (logEvent.Level >= LogEventLevel.Error)
{
var message = logEvent.RenderMessage();
dialogService.ShowToastMessage(message);
}*/
}
}
}

View File

@@ -0,0 +1,20 @@
namespace FileTime.GuiApp.Models
{
public class InputElementWrapper
{
/*public InputElement InputElement { get; }
public string Value { get; set; }
public object? Option { get; set; }
public char? PasswordChar { get; set; }
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
{
InputElement = inputElement;
Value = defaultValue ?? "";
PasswordChar = inputElement.InputType == InputType.Password ? '*' : null;
}*/
}
}

View File

@@ -1,23 +0,0 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
namespace FileTime.GuiApp
{
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()
.LogToTrace();
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp.ViewModels
{
public interface IMainWindowViewModelBase
{
bool Loading { get; }
}
}

View File

@@ -0,0 +1,16 @@
using Avalonia.Media;
namespace FileTime.GuiApp.ViewModels
{
public class ItemNamePartViewModel
{
public string Text { get; set; }
public TextDecorationCollection? TextDecorations { get; set; }
public ItemNamePartViewModel(string text, TextDecorationCollection? textDecorations)
{
Text = text;
TextDecorations = textDecorations;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp.ViewModels
{
public class MainWindowLoadingViewModel : IMainWindowViewModelBase
{
public bool Loading => true;
}
}

View File

@@ -1,10 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Input;
using FileTime.App.Core;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
using FileTime.Core.Services;
using FileTime.Providers.Local;
using Microsoft.Extensions.DependencyInjection;
using MvvmGen;
namespace FileTime.GuiApp.ViewModels
{
public class MainWindowViewModel
[ViewModel]
[Inject(typeof(IAppState), "_appState")]
[Inject(typeof(ILocalContentProvider), "_localContentProvider")]
[Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")]
public partial class MainWindowViewModel : IMainWindowViewModelBase
{
public bool Loading => false;
public IAppState AppState => _appState;
partial void OnInitialize()
{
if (AppState.Tabs.Count == 0)
{
var tab = _serviceProvider.GetInitableResolver<IContainer>(_localContentProvider).GetRequiredService<ITab>();
var tabViewModel = _serviceProvider.GetInitableResolver(tab).GetRequiredService<ITabViewModel>();
_appState.AddTab(tabViewModel);
}
}
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{
}
}
}

View File

@@ -0,0 +1,89 @@
<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:appcoreenums="using:FileTime.App.Core.Models.Enums"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="FileTime.GuiApp.Views.ItemView"
x:Name="ItemRoot"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{Binding ViewMode,Converter={StaticResource ItemViewModeToBackgroundConverter}}">
<Grid ColumnDefinitions="20,*,Auto" Margin="3" x:Name="RootGrid">
<Grid.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{Binding DataContext.ViewMode,Converter={StaticResource ItemViewModeToForegroundConverter},ElementName=ItemRoot}"/>
</Style>
</Grid.Styles>
<!--Image
Width="18"
Height="18"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Source="{Binding Converter={StaticResource ItemToImageConverter}}" /-->
<ItemsControl
Margin="5,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<ItemsControl.Items>
<MultiBinding Converter="{StaticResource NamePartShrinkerConverter}">
<MultiBinding.Bindings>
<Binding Path="DisplayName^"/>
<Binding ElementName="RootGrid" Path="Bounds.Width"/>
<Binding ElementName="ItemRoot" Path="ShowAttributes"/>
</MultiBinding.Bindings>
</MultiBinding>
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Text}"
TextDecorations="{Binding TextDecorations}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
<Grid ColumnDefinitions="50,50,90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.File}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="3" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="4" Text="{Binding BaseItem.Attributes}"/>
</Grid>
<Grid ColumnDefinitions="90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.Container}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Item.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.Attributes}"/>
</Grid>
<Grid ColumnDefinitions="50,90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.SizeContainer}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
</Grid>
<Grid ColumnDefinitions="50,50"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.Element}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,21 @@
using Avalonia;
using Avalonia.Controls;
namespace FileTime.GuiApp.Views
{
public partial class ItemView : UserControl
{
public static readonly StyledProperty<bool> ShowAttributesProperty = AvaloniaProperty.Register<ItemView, bool>(nameof(ShowAttributes), true);
public bool ShowAttributes
{
get => GetValue(ShowAttributesProperty);
set => SetValue(ShowAttributesProperty, value);
}
public ItemView()
{
InitializeComponent();
}
}
}

View File

@@ -1,17 +1,54 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:local="using:FileTime.GuiApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
InputElement.KeyDown="OnKeyDown"
Opened="OnWindowOpened"
x:Class="FileTime.GuiApp.Views.MainWindow"
Icon="/Assets/filetime.ico"
Title="FileTime.GuiApp">
Title="FileTime"
TransparencyLevelHint="Blur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"
x:DataType="vm:MainWindowViewModel"
x:CompileBindings="True">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid Background="{DynamicResource AppBackgroundBrush}">
<Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}">
<Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*">
<ListBox
Grid.Row="1"
x:Name="CurrentItems"
x:CompileBindings="False"
AutoScrollToSelectedItem="True"
IsTabStop="True"
Items="{Binding AppState.SelectedTab.CurrentItems^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Classes="ContentListView">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Borders-->
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<Grid IsVisible="{Binding Loading}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Assets/filetime.ico" Width="128" Height="128"/>
<TextBlock Text="Loading..." HorizontalAlignment="Center" Margin="50"/>
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@@ -1,12 +1,53 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using FileTime.GuiApp.Models;
using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp.Views
{
public partial class MainWindow : Window
{
private readonly ILogger<MainWindow>? _logger;
private InputElementWrapper? _inputElementWrapper;
public MainWindowViewModel? ViewModel
{
get => DataContext as MainWindowViewModel;
set
{
if (value != DataContext)
{
DataContext = value;
}
}
}
public MainWindow()
{
_logger = DI.ServiceProvider.GetService<ILogger<MainWindow>>();
_logger?.LogInformation($"Starting {nameof(MainWindow)} initialization...");
InitializeComponent();
}
private void OnWindowOpened(object sender, EventArgs e)
{
if (DataContext is not MainWindowViewModel)
{
_logger?.LogInformation($"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization...");
ViewModel = DI.ServiceProvider.GetRequiredService<MainWindowViewModel>();
}
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (_inputElementWrapper == null)
{
ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h);
}
}
}
}