diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5056327 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,128 @@ +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +insert_final_newline = true +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent + +[*.cs] +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_space_around_binary_operators = before_and_after +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_prefer_readonly_struct = true:suggestion +csharp_prefer_static_local_function = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent \ No newline at end of file diff --git a/src/Vdcrpt.BuiltIns/Effects/BinaryRepeatEffect.cs b/src/Vdcrpt.BuiltIns/Effects/BinaryRepeatEffect.cs index 23a79ef..057a82e 100644 --- a/src/Vdcrpt.BuiltIns/Effects/BinaryRepeatEffect.cs +++ b/src/Vdcrpt.BuiltIns/Effects/BinaryRepeatEffect.cs @@ -62,4 +62,4 @@ public void Apply(EffectContext context, string inputPath, string outputPath) writer.Write(data, lastEnd, data.Length - lastEnd); } -} \ No newline at end of file +} diff --git a/src/Vdcrpt.BuiltIns/Vdcrpt.BuiltIns.csproj b/src/Vdcrpt.BuiltIns/Vdcrpt.BuiltIns.csproj index 9e3f488..ac8d0b9 100644 --- a/src/Vdcrpt.BuiltIns/Vdcrpt.BuiltIns.csproj +++ b/src/Vdcrpt.BuiltIns/Vdcrpt.BuiltIns.csproj @@ -1,13 +1,13 @@ - - net6.0 - enable - enable - + + net6.0 + enable + enable + - - - + + + diff --git a/src/Vdcrpt.CommandLine/Vdcrpt.CommandLine.csproj b/src/Vdcrpt.CommandLine/Vdcrpt.CommandLine.csproj index 7d58025..55e46a7 100644 --- a/src/Vdcrpt.CommandLine/Vdcrpt.CommandLine.csproj +++ b/src/Vdcrpt.CommandLine/Vdcrpt.CommandLine.csproj @@ -6,12 +6,12 @@ - - + + - + diff --git a/src/Vdcrpt.Desktop/App.axaml b/src/Vdcrpt.Desktop/App.axaml index 8c292c1..f9dc199 100644 --- a/src/Vdcrpt.Desktop/App.axaml +++ b/src/Vdcrpt.Desktop/App.axaml @@ -1,8 +1,12 @@ + + + - + - + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/App.axaml.cs b/src/Vdcrpt.Desktop/App.axaml.cs index beff838..2c139b0 100644 --- a/src/Vdcrpt.Desktop/App.axaml.cs +++ b/src/Vdcrpt.Desktop/App.axaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Vdcrpt.Desktop.ViewModels; +using Vdcrpt.Desktop.Views; namespace Vdcrpt.Desktop; diff --git a/src/Vdcrpt.Desktop/DelegateCommand.cs b/src/Vdcrpt.Desktop/DelegateCommand.cs index 7f6e431..2b0e59c 100644 --- a/src/Vdcrpt.Desktop/DelegateCommand.cs +++ b/src/Vdcrpt.Desktop/DelegateCommand.cs @@ -3,7 +3,7 @@ namespace Vdcrpt.Desktop; -class DelegateCommand : ICommand +internal class DelegateCommand : ICommand { private readonly Func _doCanExecute; private readonly Action _doExecute; @@ -18,9 +18,20 @@ public DelegateCommand(Func doCanExecute, Action doExecu _doExecute = doExecute; } - public bool CanExecute(object? parameter) => _doCanExecute(parameter); - public void Execute(object? parameter) => _doExecute(parameter); - public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + public bool CanExecute(object? parameter) + { + return _doCanExecute(parameter); + } + + public void Execute(object? parameter) + { + _doExecute(parameter); + } public event EventHandler? CanExecuteChanged; + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } } \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/MainWindow.axaml b/src/Vdcrpt.Desktop/MainWindow.axaml deleted file mode 100644 index 536c506..0000000 --- a/src/Vdcrpt.Desktop/MainWindow.axaml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - vdcrpt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Randomize - - - - - - - - - - Open video when done - Ask where to save every time - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/MainWindowViewModel.cs b/src/Vdcrpt.Desktop/MainWindowViewModel.cs deleted file mode 100644 index 2e2cfe2..0000000 --- a/src/Vdcrpt.Desktop/MainWindowViewModel.cs +++ /dev/null @@ -1,388 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using System.Windows.Input; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data; -using FFMpegCore.Exceptions; -using JetBrains.Annotations; -using Vdcrpt; -using Vdcrpt.BuiltIns.Effects; - -namespace Vdcrpt.Desktop; - -// TODO: This should be broken into smaller components -public sealed class MainWindowViewModel : INotifyPropertyChanged -{ - private const string DefaultProgressMessage = "Ready!"; - - private readonly BackgroundWorker _corruptWorker; - - private string _inputPath = string.Empty; - private Preset _currentPreset; - private int _burstSize = 5000; - private int _iterations = 5; - private int _minTrailLength = 10; - private int _maxTrailLength = 20; - - // TODO: Avalonia lets us bind directly to methods, commands not always necessary - private DelegateCommand _onOpenResultPressed; - private bool _openWhenComplete = false; - private bool _askForFilename = true; - private string _outputPath = string.Empty; - - private int _progressAmount; - private string _progressMessage = DefaultProgressMessage; - - #region Properties - - public ICommand OpenUrl { get; } = new DelegateCommand(url => - { - if (url is not string urlString) return; - - Process.Start(new ProcessStartInfo - { - FileName = urlString, - UseShellExecute = true - }); - }); - - public bool CanStartCorrupting => File.Exists(InputPath) && !IsBusy; - - public bool IsBusy => _corruptWorker.IsBusy; - - public string InputPath - { - get => _inputPath; - set - { - if (value == _inputPath) return; - _inputPath = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(CanStartCorrupting)); - - if (!File.Exists(_inputPath)) throw new DataValidationException("File does not exist."); - } - } - - [Range(1, int.MaxValue)] - public int BurstSize - { - get => _burstSize; - set - { - if (value == _burstSize) return; - _burstSize = value; - OnPropertyChanged(); - } - } - - [Range(1, int.MaxValue)] - public int MinTrailLength - { - get => _minTrailLength; - set - { - if (value == _minTrailLength) return; - _minTrailLength = value; - OnPropertyChanged(); - - if (_maxTrailLength < _minTrailLength) _maxTrailLength = _minTrailLength; - OnPropertyChanged(nameof(MaxTrailLength)); - } - } - - [Range(1, int.MaxValue)] - public int MaxTrailLength - { - get => _maxTrailLength; - set - { - if (value == _maxTrailLength) return; - _maxTrailLength = value; - OnPropertyChanged(); - - if (_minTrailLength > _maxTrailLength) _minTrailLength = _maxTrailLength; - OnPropertyChanged(nameof(MinTrailLength)); - } - } - - private bool _useTrailLengthRange; - public int MinTrailLengthColumnSpan => _useTrailLengthRange ? 1 : 3; - - public bool UseTrailLengthRange - { - get => _useTrailLengthRange; - set - { - if (value == _useTrailLengthRange) return; - _useTrailLengthRange = value; - - OnPropertyChanged(); - OnPropertyChanged(nameof(MinTrailLengthColumnSpan)); - } - } - - [Range(1, int.MaxValue)] - public int Iterations - { - get => _iterations; - set - { - if (value == _iterations) return; - _iterations = value; - OnPropertyChanged(); - } - } - - public bool OpenWhenComplete - { - get => _openWhenComplete; - set - { - if (value == _openWhenComplete) return; - _openWhenComplete = value; - OnPropertyChanged(); - } - } - - public bool AskForFilename - { - get => _askForFilename; - set - { - if (value == _askForFilename) return; - _askForFilename = value; - OnPropertyChanged(); - } - } - - public int ProgressAmount - { - get => _progressAmount; - set - { - if (value == _progressAmount) return; - _progressAmount = value; - OnPropertyChanged(); - } - } - - public string ProgressMessage - { - get => _progressMessage; - set - { - if (value == _progressMessage) return; - _progressMessage = value; - OnPropertyChanged(); - } - } - - public string OutputPath - { - get => _outputPath; - set - { - if (value == _outputPath) return; - _outputPath = value; - OnPropertyChanged(); - _onOpenResultPressed.RaiseCanExecuteChanged(); - } - } - - public string VersionText - { - get - { - var version = Assembly.GetExecutingAssembly().GetName().Version; - var versionString = version is not null - ? $"{version.Major:00}.{version.Minor:00}.{version.Build:00}" - : "UNKNOWN"; -#if DEBUG - versionString += " (DEBUG)"; -#endif - - return $"Version {versionString}"; - } - } - - // TODO: User-defined presets - public List Presets { get; } = Preset.DefaultPresets; - - public Preset CurrentPreset - { - get => _currentPreset; - set - { - if (value == _currentPreset) return; - _currentPreset = value; - OnPropertyChanged(); - - BurstSize = _currentPreset.BurstSize; - MinTrailLength = _currentPreset.MinBurstLength; - - UseTrailLengthRange = _currentPreset.UseLengthRange; - if (_currentPreset.UseLengthRange) MaxTrailLength = _currentPreset.MaxBurstLength; - - Iterations = _currentPreset.Iterations; - } - } - - #endregion - - - public ICommand OnOpenResultPressed => _onOpenResultPressed; - - public ICommand OnExitPressed { get; } = new DelegateCommand(_ => - { - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.Shutdown(); - } - else - { - // Not sure what's best to do here, but we shouldn't ever reach this branch anyway. - Environment.Exit(0); - } - }); - - public MainWindowViewModel() - { - _currentPreset = CurrentPreset = Presets[0]; - - _corruptWorker = new BackgroundWorker(); - _corruptWorker.WorkerReportsProgress = true; - - _onOpenResultPressed = new DelegateCommand( - _ => File.Exists(_outputPath) && !_corruptWorker.IsBusy, - _ => new Process - { - StartInfo = new ProcessStartInfo(OutputPath) - { - UseShellExecute = true - } - }.Start() - ); - - _corruptWorker.DoWork += DoBackgroundWork; - _corruptWorker.ProgressChanged += (_, args) => - { - ProgressAmount = args.ProgressPercentage; - if (args.UserState is string state) - { - ProgressMessage = state; - } - }; - - _corruptWorker.RunWorkerCompleted += (_, args) => - { - ProgressAmount = 0; - - ProgressMessage = args.Error switch - { - FFMpegException { Type: FFMpegExceptionType.Process } => - "The video failed to render. Try again or lower the settings.", - FFMpegException { Type: FFMpegExceptionType.Operation } => - $"FFmpeg did not behave as expected. Redownload vdcrpt or file a bug report if the error persists: {args.Error.Message}", - not null => $"An unexpected error occurred: {args.Error.Message}", - null => $"Done! Saved at {_outputPath}.", - }; - - _onOpenResultPressed.RaiseCanExecuteChanged(); - - OnPropertyChanged(nameof(IsBusy)); - OnPropertyChanged(nameof(CanStartCorrupting)); - - if (args.Error is null && OpenWhenComplete) _onOpenResultPressed.Execute(null); - }; - } - - private static string GenerateOutputPath(string inputPath) - { - var pathNoExt = Path.ChangeExtension(inputPath, null); - var pathTimestampNoExt = $"{pathNoExt}_vdcrpt"; - - string result; - var increment = 0; - - do - { - result = Path.ChangeExtension($"{pathTimestampNoExt}_{increment++:00}", "mp4"); - } while (File.Exists(result)); - - return result; - } - - public async Task StartCorrupting() - { - if (Application.Current?.ApplicationLifetime is not ClassicDesktopStyleApplicationLifetime app) return; - - if (!AskForFilename || string.IsNullOrEmpty(OutputPath)) - { - OutputPath = GenerateOutputPath(_inputPath); - } - - if (AskForFilename) - { - var dialog = new SaveFileDialog - { - Directory = Path.GetDirectoryName(OutputPath), - InitialFileName = Path.GetFileName(OutputPath), - Filters = new List - { - new FileDialogFilter { Extensions = { "mp4" }, Name = "MP4 video" }, - }, - DefaultExtension = "mp4", - }; - - var chosenPath = await dialog.ShowAsync(app.MainWindow); - - if (string.IsNullOrEmpty(chosenPath)) - { - ProgressMessage = DefaultProgressMessage; - return; - } - - OutputPath = chosenPath; - } - - _corruptWorker.RunWorkerAsync(); - OnPropertyChanged(nameof(IsBusy)); - OnPropertyChanged(nameof(CanStartCorrupting)); - } - - private void DoBackgroundWork(object? sender, DoWorkEventArgs args) - { - if (sender is not BackgroundWorker worker) return; - - var effect = new BinaryRepeatEffect - { - Iterations = _iterations, - BurstSize = _burstSize, - MinBurstLength = _minTrailLength, - MaxBurstLength = UseTrailLengthRange ? _maxTrailLength : _minTrailLength, - }; - - worker.ReportProgress(50, "Corrupting data..."); - Session.ApplyEffects(_inputPath, _outputPath, effect); - - worker.ReportProgress(100, "Finishing up..."); - } - - public event PropertyChangedEventHandler? PropertyChanged; - - [NotifyPropertyChangedInvocator] - private void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } -} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Models/BinaryRepeatEffectSettings.cs b/src/Vdcrpt.Desktop/Models/BinaryRepeatEffectSettings.cs new file mode 100644 index 0000000..90f1ada --- /dev/null +++ b/src/Vdcrpt.Desktop/Models/BinaryRepeatEffectSettings.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using CommunityToolkit.Mvvm.ComponentModel; +using Vdcrpt.BuiltIns.Effects; + +namespace Vdcrpt.Desktop.Models; + +public partial class BinaryRepeatEffectSettings : ObservableValidator +{ + [ObservableProperty] [Range(1, int.MaxValue)] + private int _burstSize; + + [ObservableProperty] [Range(1, int.MaxValue)] + private int _iterations; + + [ObservableProperty] [Range(1, int.MaxValue)] + private int _maxBurstLength; + + [ObservableProperty] [Range(1, int.MaxValue)] + private int _minBurstLength; + + [ObservableProperty] private bool _useBurstLengthRange; + + public BinaryRepeatEffect ToEffectInstance() + { + return new BinaryRepeatEffect + { + Iterations = Iterations, + MinBurstLength = MinBurstLength, + MaxBurstLength = UseBurstLengthRange ? MaxBurstLength : MinBurstLength, + BurstSize = BurstSize + }; + } + + public void CopyFrom(in BinaryRepeatEffectSettings other) + { + BurstSize = other.BurstSize; + Iterations = other.Iterations; + MaxBurstLength = other.MaxBurstLength; + MinBurstLength = other.MinBurstLength; + UseBurstLengthRange = other.UseBurstLengthRange; + } + + partial void OnMinBurstLengthChanged(int value) + { + if (value > MaxBurstLength) + { + MaxBurstLength = value; + } + } + + partial void OnMaxBurstLengthChanged(int value) + { + if (value < MinBurstLength) + { + MinBurstLength = value; + } + } +} diff --git a/src/Vdcrpt.Desktop/Models/Preset.cs b/src/Vdcrpt.Desktop/Models/Preset.cs new file mode 100644 index 0000000..6d121f8 --- /dev/null +++ b/src/Vdcrpt.Desktop/Models/Preset.cs @@ -0,0 +1,13 @@ +namespace Vdcrpt.Desktop.Models; + +public class Preset +{ + public Preset() + { + Name = string.Empty; + Settings = new BinaryRepeatEffectSettings(); + } + + public string Name { get; init; } + public BinaryRepeatEffectSettings Settings { get; set; } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Models/ProgramInfo.cs b/src/Vdcrpt.Desktop/Models/ProgramInfo.cs new file mode 100644 index 0000000..b79133d --- /dev/null +++ b/src/Vdcrpt.Desktop/Models/ProgramInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; + +namespace Vdcrpt.Desktop.Models; + +public record ProgramInfo( + string Name, + string Version, + string ItchUrl, + string GitHubUrl +) +{ + public static readonly ProgramInfo Default = new( + "vdcrpt", + GetVersionFromAssembly(), + "https://branchpanic.itch.io/vdcrpt", + "https://github.com/branchpanic/vdcrpt" + ); + + private static string GetVersionFromAssembly() + { + var version = Assembly.GetExecutingAssembly().GetName().Version; + var versionString = version is not null + ? $"{version.Major:00}.{version.Minor:00}.{version.Build:00}" + : "UNKNOWN"; +#if DEBUG + versionString += " (DEBUG)"; +#endif + + return $"{versionString}"; + } +} diff --git a/src/Vdcrpt.Desktop/Models/Project.cs b/src/Vdcrpt.Desktop/Models/Project.cs new file mode 100644 index 0000000..00a4255 --- /dev/null +++ b/src/Vdcrpt.Desktop/Models/Project.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.IO; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Vdcrpt.Desktop.Models; + +public partial class Project : ObservableValidator +{ + [ObservableProperty] [CustomValidation(typeof(Project), nameof(ValidateInputFile))] + private string _inputFile; + + public Project() + { + _inputFile = string.Empty; + Config = new UserConfig(); + EffectSettings = new BinaryRepeatEffectSettings(); + } + + public UserConfig Config { get; set; } + public BinaryRepeatEffectSettings EffectSettings { get; set; } + + public static ValidationResult ValidateInputFile(string inputFile, ValidationContext context) + { + if (!File.Exists(inputFile)) + { + return new ValidationResult("File does not exist."); + } + + return ValidationResult.Success!; + } + + public void Render(string outputPath, Action reportProgress = null) + { + var effect = EffectSettings.ToEffectInstance(); + reportProgress?.Invoke(0.5, "Corrupting data..."); + Session.ApplyEffects(InputFile, outputPath, effect); + reportProgress?.Invoke(1.0, "Finishing up..."); + } + + public static string GenerateOutputPath(string inputPath) + { + var pathNoExt = Path.ChangeExtension(inputPath, null); + var pathTimestampNoExt = $"{pathNoExt}_vdcrpt"; + + string result; + var increment = 0; + + do + { + result = Path.ChangeExtension($"{pathTimestampNoExt}_{increment++:00}", "mp4"); + } while (File.Exists(result)); + + return result; + } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Models/UserConfig.cs b/src/Vdcrpt.Desktop/Models/UserConfig.cs new file mode 100644 index 0000000..5f49e9e --- /dev/null +++ b/src/Vdcrpt.Desktop/Models/UserConfig.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Vdcrpt.Desktop.Models; + +public partial class UserConfig : ObservableObject +{ + [ObservableProperty] private bool _askForFilename; + [ObservableProperty] private bool _openWhenComplete; + + public UserConfig() + { + Presets = new List(); + } + + public IReadOnlyList Presets { get; private set; } + + public static UserConfig CreateDefault() + { + return new UserConfig + { + OpenWhenComplete = false, + AskForFilename = true, + + Presets = new List + { + new() + { + Name = "Melting Chaos", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 3000, + MinBurstLength = 8, + Iterations = 400 + } + }, + new() + { + Name = "Jittery", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 20000, + MinBurstLength = 1, + MaxBurstLength = 8, + UseBurstLengthRange = true, + Iterations = 200 + } + }, + new() + { + Name = "Source Engine", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 45000, + MinBurstLength = 2, + MaxBurstLength = 6, + UseBurstLengthRange = true, + Iterations = 60 + } + }, + new() + { + Name = "Subtle", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 200, + MinBurstLength = 2, + Iterations = 60 + } + }, + new() + { + Name = "Many Artifacts", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 500, + MinBurstLength = 3, + Iterations = 2000 + } + }, + new() + { + Name = "Trash (unstable, breaks audio)", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 1, + MinBurstLength = 1, + Iterations = 10000 + } + }, + new() + { + Name = "Legacy", + Settings = new BinaryRepeatEffectSettings + { + BurstSize = 1000, + MinBurstLength = 10, + MaxBurstLength = 90, + UseBurstLengthRange = true, + Iterations = 50 + } + } + } + }; + } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Preset.cs b/src/Vdcrpt.Desktop/Preset.cs deleted file mode 100644 index 09f7a46..0000000 --- a/src/Vdcrpt.Desktop/Preset.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; - -namespace Vdcrpt.Desktop; - -public class Preset -{ - public string Name { get; init; } = string.Empty; - public int BurstSize { get; init; } - public int Iterations { get; init; } - - // TODO: Range was tacked on at the last minute, this can be - // represented more succinctly. Some of this is going to get blown up - // for user presets anyway, so it's fine for now. - // - // When not using range, MinBurstLength is the constant burst length. - public bool UseLengthRange { get; init; } - public int MinBurstLength { get; init; } - public int MaxBurstLength { get; init; } - - // TODO: User-defined presets, this will become data - public static List DefaultPresets => new() - { - new Preset { Name = "Melting Chaos", BurstSize = 3000, MinBurstLength = 8, Iterations = 400 }, - new Preset - { - Name = "Jittery", BurstSize = 20000, MinBurstLength = 1, MaxBurstLength = 8, - UseLengthRange = true, Iterations = 200 - }, - new Preset - { - Name = "Source Engine", BurstSize = 45000, MinBurstLength = 2, MaxBurstLength = 6, - UseLengthRange = true, Iterations = 60 - }, - new Preset { Name = "Subtle", BurstSize = 200, MinBurstLength = 2, Iterations = 60 }, - new Preset { Name = "Many Artifacts", BurstSize = 500, MinBurstLength = 3, Iterations = 2000 }, - new Preset - { - Name = "Trash (unstable, breaks audio)", BurstSize = 1, MinBurstLength = 1, Iterations = 10000 - }, - new Preset - { - Name = "Legacy", BurstSize = 1000, MinBurstLength = 10, MaxBurstLength = 90, - UseLengthRange = true, Iterations = 50 - }, - }; -} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Program.cs b/src/Vdcrpt.Desktop/Program.cs index 7357d69..c6734eb 100644 --- a/src/Vdcrpt.Desktop/Program.cs +++ b/src/Vdcrpt.Desktop/Program.cs @@ -3,18 +3,23 @@ namespace Vdcrpt.Desktop; -class Program +internal 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); + 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() + { + return AppBuilder.Configure() .UsePlatformDetect() .LogToTrace(); + } } \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Vdcrpt.Desktop.csproj b/src/Vdcrpt.Desktop/Vdcrpt.Desktop.csproj index fb02ec7..d3f33da 100644 --- a/src/Vdcrpt.Desktop/Vdcrpt.Desktop.csproj +++ b/src/Vdcrpt.Desktop/Vdcrpt.Desktop.csproj @@ -1,4 +1,4 @@ - + WinExe net6.0 @@ -11,8 +11,7 @@ false true true - true - enable + true branchpanic vdcrpt @@ -21,31 +20,35 @@ $(Version) Assets/Icon.ico - + - - - + + + + - + - + - - + + - + - + - + - - MainWindow.axaml - Code - + + EffectSettingsView.axaml + + + MainWindow.axaml + Code + - + vdcrpt vdcrpt @@ -57,4 +60,12 @@ NSApplication true + + + full + + + + full + diff --git a/src/Vdcrpt.Desktop/ViewLocator.cs b/src/Vdcrpt.Desktop/ViewLocator.cs new file mode 100644 index 0000000..6c69c17 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewLocator.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +namespace Vdcrpt.Desktop; + +public class ViewLocator : IDataTemplate +{ + public bool SupportsRecycling => false; + + public IControl Build(object data) + { + var name = data.GetType().FullName.Replace("ViewModel", "View"); + 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; + } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/ViewModelBase.cs b/src/Vdcrpt.Desktop/ViewModelBase.cs new file mode 100644 index 0000000..13fe348 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Vdcrpt.Desktop; + +public abstract class ViewModelBase : ObservableValidator +{ +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/ViewModels/EffectSettingsViewModel.cs b/src/Vdcrpt.Desktop/ViewModels/EffectSettingsViewModel.cs new file mode 100644 index 0000000..f625990 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModels/EffectSettingsViewModel.cs @@ -0,0 +1,25 @@ +using Vdcrpt.Desktop.Models; + +namespace Vdcrpt.Desktop.ViewModels; + +public class EffectSettingsViewModel : ViewModelBase +{ + public EffectSettingsViewModel(BinaryRepeatEffectSettings settings) + { + Settings = settings; + Settings.PropertyChanged += (_, args) => + { + if (args.PropertyName == nameof(Settings.UseBurstLengthRange)) + { + OnPropertyChanged(nameof(MinBurstLengthColumnSpan)); + } + }; + } + + public EffectSettingsViewModel() : this(new BinaryRepeatEffectSettings()) + { + } + + public BinaryRepeatEffectSettings Settings { get; } + public int MinBurstLengthColumnSpan => Settings.UseBurstLengthRange ? 1 : 3; +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/ViewModels/MainWindowViewModel.cs b/src/Vdcrpt.Desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..d3042f5 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using CommunityToolkit.Mvvm.ComponentModel; +using Vdcrpt.Desktop.Models; + +namespace Vdcrpt.Desktop.ViewModels; + +public partial class MainWindowViewModel : ViewModelBase +{ + [ObservableProperty] private Project _project; + + public MainWindowViewModel() + { + var config = UserConfig.CreateDefault(); + + _project = new Project + { + InputFile = string.Empty, + Config = config, + EffectSettings = new BinaryRepeatEffectSettings() + }; + + ProjectViewModel = new ProjectViewModel(_project); + } + + public ProgramInfo ProgramInfo => ProgramInfo.Default; + public string VersionWithPrefix => string.Concat("Version ", ProgramInfo.Version); + + public ICommand OnExitPressed { get; } = new DelegateCommand(_ => + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.Shutdown(); + } + else + { + // Not sure what's best to do here, but we shouldn't ever reach this branch anyway. + Environment.Exit(0); + } + }); + + public ProjectViewModel ProjectViewModel { get; } + + public void OpenUrl(string url) + { + Process.Start(new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + } +} diff --git a/src/Vdcrpt.Desktop/ViewModels/OutputSettingsViewModel.cs b/src/Vdcrpt.Desktop/ViewModels/OutputSettingsViewModel.cs new file mode 100644 index 0000000..58f7140 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModels/OutputSettingsViewModel.cs @@ -0,0 +1,18 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Vdcrpt.Desktop.Models; + +namespace Vdcrpt.Desktop.ViewModels; + +public partial class OutputSettingsViewModel : ViewModelBase +{ + [ObservableProperty] private UserConfig _userConfig; + + public OutputSettingsViewModel(UserConfig userConfig) + { + _userConfig = userConfig; + } + + public OutputSettingsViewModel() : this(new UserConfig()) + { + } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/ViewModels/PresetEffectSettingsViewModel.cs b/src/Vdcrpt.Desktop/ViewModels/PresetEffectSettingsViewModel.cs new file mode 100644 index 0000000..02ae46a --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModels/PresetEffectSettingsViewModel.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; +using Vdcrpt.Desktop.Models; + +namespace Vdcrpt.Desktop.ViewModels; + +public partial class PresetEffectSettingsViewModel : ViewModelBase +{ + [ObservableProperty] private Preset _currentPreset; + + public PresetEffectSettingsViewModel(IReadOnlyList presets, BinaryRepeatEffectSettings settings) + { + Presets = presets; + EffectSettingsViewModel = new EffectSettingsViewModel(settings); + + if (presets.Count > 0) + { + CurrentPreset = presets[0]; + } + } + + public PresetEffectSettingsViewModel() : this(new List(), new BinaryRepeatEffectSettings()) + { + } + + public IReadOnlyList Presets { get; } + public EffectSettingsViewModel EffectSettingsViewModel { get; } + + partial void OnCurrentPresetChanged(Preset value) + { + if (value is null) + { + return; + } + + EffectSettingsViewModel.Settings.CopyFrom(value.Settings); + } +} diff --git a/src/Vdcrpt.Desktop/ViewModels/ProjectViewModel.cs b/src/Vdcrpt.Desktop/ViewModels/ProjectViewModel.cs new file mode 100644 index 0000000..c6a2ed8 --- /dev/null +++ b/src/Vdcrpt.Desktop/ViewModels/ProjectViewModel.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Vdcrpt.Desktop.Models; + +namespace Vdcrpt.Desktop.ViewModels; + +public partial class ProjectViewModel : ViewModelBase +{ + private const string DefaultProgressMessage = "Ready!"; + + [ObservableProperty] private bool _isBusy; + + // TODO: Maybe part of model? + [ObservableProperty] private string _outputPath; + [ObservableProperty] private double _progressAmount; + [ObservableProperty] private string _progressMessage; + [ObservableProperty] private bool _running; + + public ProjectViewModel(Project project) + { + Project = project; + PresetEffectSettingsViewModel = + new PresetEffectSettingsViewModel(project.Config.Presets, project.EffectSettings); + OutputSettingsViewModel = new OutputSettingsViewModel(project.Config); + + Project.PropertyChanged += (_, args) => + { + if (args.PropertyName == nameof(Project.InputFile)) + { + StartCorruptingCommand.NotifyCanExecuteChanged(); + } + }; + } + + public ProjectViewModel() : this(new Project()) + { + } + + public Project Project { get; } + public PresetEffectSettingsViewModel PresetEffectSettingsViewModel { get; } + public OutputSettingsViewModel OutputSettingsViewModel { get; } + + [RelayCommand(CanExecute = nameof(CanOpenResult))] + private void OpenResult() + { + new Process + { + StartInfo = new ProcessStartInfo(OutputPath) + { + UseShellExecute = true + } + }.Start(); + } + + private bool CanOpenResult() + { + return File.Exists(OutputPath); + } + + [RelayCommand(CanExecute = nameof(CanStartCorrupting))] + private async Task StartCorrupting() + { + if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) + { + return; + } + + if (Project.Config.AskForFilename) + { + var dialog = new SaveFileDialog + { + Directory = Path.GetDirectoryName(OutputPath), + InitialFileName = Path.GetFileName(OutputPath), + Filters = new List + { + new() { Extensions = { "mp4" }, Name = "MP4 video" } + }, + DefaultExtension = "mp4" + }; + + var chosenPath = await dialog.ShowAsync(desktop.MainWindow); + + if (string.IsNullOrEmpty(chosenPath)) + { + ProgressMessage = DefaultProgressMessage; + return; + } + + OutputPath = chosenPath; + } + else + { + OutputPath = Project.GenerateOutputPath(Project.InputFile); + } + + try + { + Running = true; + await Task.Run(() => Project.Render(OutputPath, (progress, message) => + { + ProgressMessage = message; + ProgressAmount = progress; + })); + } + finally + { + Running = false; + OpenResultCommand.NotifyCanExecuteChanged(); + } + + if (Project.Config.OpenWhenComplete) + { + OpenResult(); + } + } + + private bool CanStartCorrupting() + { + return !Running && File.Exists(Project.InputFile); + } +} diff --git a/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml b/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml new file mode 100644 index 0000000..5ebeb06 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + Randomize + + + + + + + + + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml.cs b/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml.cs new file mode 100644 index 0000000..7fe3f39 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/EffectSettingsView.axaml.cs @@ -0,0 +1,7 @@ +using Avalonia.Controls; + +namespace Vdcrpt.Desktop.Views; + +public class EffectSettingsView : UserControl +{ +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/MainWindow.axaml b/src/Vdcrpt.Desktop/Views/MainWindow.axaml new file mode 100644 index 0000000..0ce6031 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/MainWindow.axaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/MainWindow.axaml.cs b/src/Vdcrpt.Desktop/Views/MainWindow.axaml.cs similarity index 69% rename from src/Vdcrpt.Desktop/MainWindow.axaml.cs rename to src/Vdcrpt.Desktop/Views/MainWindow.axaml.cs index 92651c0..0127bba 100644 --- a/src/Vdcrpt.Desktop/MainWindow.axaml.cs +++ b/src/Vdcrpt.Desktop/Views/MainWindow.axaml.cs @@ -6,11 +6,11 @@ using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -namespace Vdcrpt.Desktop; +namespace Vdcrpt.Desktop.Views; -public partial class MainWindow : Window +public class MainWindow : Window { - private TextBox _inputPathTextBox; + private readonly TextBox _inputPathTextBox; public MainWindow() { @@ -28,10 +28,16 @@ public MainWindow() private void OnDrop(object? sender, DragEventArgs e) { var filenames = e.Data.GetFileNames(); - if (filenames == null) return; + if (filenames == null) + { + return; + } var filenamesList = filenames.ToList(); - if (filenamesList.Count <= 0) return; + if (filenamesList.Count <= 0) + { + return; + } // Propagates to viewmodel _inputPathTextBox.Text = filenamesList[0]; @@ -50,13 +56,16 @@ private async void OnOpenPressed(object? sender, RoutedEventArgs routedEventArgs AllowMultiple = false, Filters = new List { - new FileDialogFilter { Name = "Common Video Files", Extensions = { "mp4", "avi", "mkv", "mov", "gif" } }, - new FileDialogFilter { Name = "All Files", Extensions = { "*" } }, + new() { Name = "Common Video Files", Extensions = { "mp4", "avi", "mkv", "mov", "gif" } }, + new() { Name = "All Files", Extensions = { "*" } } } }; var result = await dialog.ShowAsync(this); - if (result is not { Length: > 0 }) return; + if (result is not { Length: > 0 }) + { + return; + } // Propagates to viewmodel _inputPathTextBox.Text = result[0]; diff --git a/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml b/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml new file mode 100644 index 0000000..b883e5a --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml @@ -0,0 +1,16 @@ + + + + + + + Open video when done + Ask where to save every time + + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml.cs b/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml.cs new file mode 100644 index 0000000..8fbcd3b --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/OutputSettingsView.axaml.cs @@ -0,0 +1,7 @@ +using Avalonia.Controls; + +namespace Vdcrpt.Desktop.Views; + +public class OutputSettingsView : UserControl +{ +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml b/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml new file mode 100644 index 0000000..e50a140 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml.cs b/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml.cs new file mode 100644 index 0000000..6f396a1 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/PresetEffectSettingsView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Vdcrpt.Desktop.Views; + +public class PresetEffectSettingsView : UserControl +{ + public PresetEffectSettingsView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/ProjectView.axaml b/src/Vdcrpt.Desktop/Views/ProjectView.axaml new file mode 100644 index 0000000..0508f16 --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/ProjectView.axaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Vdcrpt.Desktop/Views/ProjectView.axaml.cs b/src/Vdcrpt.Desktop/Views/ProjectView.axaml.cs new file mode 100644 index 0000000..cccbaee --- /dev/null +++ b/src/Vdcrpt.Desktop/Views/ProjectView.axaml.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace Vdcrpt.Desktop.Views; + +public class ProjectView : UserControl +{ + private readonly TextBox _inputPathTextBox; + + public ProjectView() + { + InitializeComponent(); + + _inputPathTextBox = this.Find("InputPathTextBox"); + AddHandler(DragDrop.DropEvent, OnDrop); + } + + private void OnDrop(object? sender, DragEventArgs e) + { + var filenames = e.Data.GetFileNames(); + if (filenames == null) + { + return; + } + + var filenamesList = filenames.ToList(); + if (filenamesList.Count <= 0) + { + return; + } + + // Propagates to viewmodel + _inputPathTextBox.Text = filenamesList[0]; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + // TODO: Rewrite this as a service + private async void OnOpenPressed(object? sender, RoutedEventArgs routedEventArgs) + { + if (Application.Current!.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktopLifetime) + { + return; + } + + var dialog = new OpenFileDialog + { + Directory = ".", + AllowMultiple = false, + Filters = new List + { + new() { Name = "Common Video Files", Extensions = { "mp4", "avi", "mkv", "mov", "gif" } }, + new() { Name = "All Files", Extensions = { "*" } } + } + }; + + var result = await dialog.ShowAsync(desktopLifetime.MainWindow); + if (result is not { Length: > 0 }) + { + return; + } + + // Propagates to viewmodel + _inputPathTextBox.Text = result[0]; + } +} \ No newline at end of file diff --git a/src/Vdcrpt/Vdcrpt.csproj b/src/Vdcrpt/Vdcrpt.csproj index 8920b7f..91513dd 100644 --- a/src/Vdcrpt/Vdcrpt.csproj +++ b/src/Vdcrpt/Vdcrpt.csproj @@ -1,13 +1,13 @@ - - net6.0 - enable - enable - + + net6.0 + enable + enable + - - - + + + diff --git a/tests/Vdcrpt.Tests/Vdcrpt.Tests.csproj b/tests/Vdcrpt.Tests/Vdcrpt.Tests.csproj index b8602d0..6952770 100644 --- a/tests/Vdcrpt.Tests/Vdcrpt.Tests.csproj +++ b/tests/Vdcrpt.Tests/Vdcrpt.Tests.csproj @@ -1,28 +1,28 @@ - - net6.0 - enable - enable + + net6.0 + enable + enable - false - + false + - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - + + +