From dadfc8194c706d8b9d3f55289d52bdf8a73843e0 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Fri, 27 Dec 2024 01:51:12 +0900 Subject: [PATCH 01/10] change: rename AddOutputQueueDialog to AddOutputProfileDialog --- src/Beutl/Pages/AddOutputQueueDialog.axaml | 75 ----------------- src/Beutl/Pages/AddOutputQueueDialog.axaml.cs | 74 ----------------- .../Dialogs/AddOutputProfileViewModel.cs | 33 ++++++++ .../Dialogs/AddOutputQueueViewModel.cs | 81 ------------------- .../Dialogs/AddOutputProfileDialog.axaml | 39 +++++++++ .../Dialogs/AddOutputProfileDialog.axaml.cs | 21 +++++ 6 files changed, 93 insertions(+), 230 deletions(-) delete mode 100644 src/Beutl/Pages/AddOutputQueueDialog.axaml delete mode 100644 src/Beutl/Pages/AddOutputQueueDialog.axaml.cs create mode 100644 src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs delete mode 100644 src/Beutl/ViewModels/Dialogs/AddOutputQueueViewModel.cs create mode 100644 src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml create mode 100644 src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml.cs diff --git a/src/Beutl/Pages/AddOutputQueueDialog.axaml b/src/Beutl/Pages/AddOutputQueueDialog.axaml deleted file mode 100644 index 2e0404367..000000000 --- a/src/Beutl/Pages/AddOutputQueueDialog.axaml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Beutl/Pages/AddOutputQueueDialog.axaml.cs b/src/Beutl/Pages/AddOutputQueueDialog.axaml.cs deleted file mode 100644 index 562f59d72..000000000 --- a/src/Beutl/Pages/AddOutputQueueDialog.axaml.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; - -using Beutl.ViewModels.Dialogs; - -using FluentAvalonia.UI.Controls; - -namespace Beutl.Pages; - -public partial class AddOutputQueueDialog : ContentDialog -{ - private IDisposable? _sBtnBinding; - - public AddOutputQueueDialog() - { - InitializeComponent(); - } - - protected override Type StyleKeyOverride => typeof(ContentDialog); - - protected override void OnPrimaryButtonClick(ContentDialogButtonClickEventArgs args) - { - base.OnPrimaryButtonClick(args); - if (carousel.SelectedIndex == 1) - { - args.Cancel = true; - IsPrimaryButtonEnabled = false; - _sBtnBinding?.Dispose(); - SecondaryButtonText = Strings.Next; - IsSecondaryButtonEnabled = true; - carousel.Previous(); - } - } - - protected override void OnSecondaryButtonClick(ContentDialogButtonClickEventArgs args) - { - base.OnSecondaryButtonClick(args); - if (DataContext is not AddOutputQueueViewModel vm) return; - - if (carousel.SelectedIndex == 1) - { - vm.Add(); - } - else - { - args.Cancel = true; - - IsPrimaryButtonEnabled = true; - _sBtnBinding = Bind(IsSecondaryButtonEnabledProperty, vm.CanAdd); - SecondaryButtonText = Strings.Add; - carousel.Next(); - } - } - - // 場所を選択 - private async void OpenFileClick(object? sender, RoutedEventArgs e) - { - if (DataContext is AddOutputQueueViewModel vm && VisualRoot is TopLevel parent) - { - var options = new FilePickerOpenOptions() - { - FileTypeFilter = vm.GetFilePickerFileTypes() - }; - IReadOnlyList result = await parent.StorageProvider.OpenFilePickerAsync(options); - - if (result.Count > 0 - && result[0].TryGetLocalPath() is string localPath) - { - vm.SelectedFile.Value = localPath; - } - } - } -} diff --git a/src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs b/src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs new file mode 100644 index 000000000..2d85dbb7a --- /dev/null +++ b/src/Beutl/ViewModels/Dialogs/AddOutputProfileViewModel.cs @@ -0,0 +1,33 @@ +using Beutl.Services; +using Beutl.ViewModels.Tools; +using Reactive.Bindings; + +namespace Beutl.ViewModels.Dialogs; + +public sealed class AddOutputProfileViewModel +{ + private readonly OutputTabViewModel _outputTabViewModel; + + public AddOutputProfileViewModel(OutputTabViewModel outputTabViewModel) + { + _outputTabViewModel = outputTabViewModel; + AvailableExtensions = OutputService.GetExtensions(outputTabViewModel.EditViewModel.Scene.FileName); + + CanAdd = SelectedExtension.Select(x => x != null) + .ToReadOnlyReactivePropertySlim(); + } + + public ReadOnlyReactivePropertySlim CanAdd { get; } + + public ReactiveProperty SelectedExtension { get; } = new(); + + public OutputExtension[] AvailableExtensions { get; } + + public void Add() + { + if (SelectedExtension.Value != null) + { + _outputTabViewModel.AddItem(SelectedExtension.Value); + } + } +} diff --git a/src/Beutl/ViewModels/Dialogs/AddOutputQueueViewModel.cs b/src/Beutl/ViewModels/Dialogs/AddOutputQueueViewModel.cs deleted file mode 100644 index 20d1aea0a..000000000 --- a/src/Beutl/ViewModels/Dialogs/AddOutputQueueViewModel.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections.ObjectModel; - -using Avalonia.Platform.Storage; - -using Beutl.Api.Services; -using Beutl.Services; - -using DynamicData; -using DynamicData.Binding; - -using Reactive.Bindings; - -namespace Beutl.ViewModels.Dialogs; - -public sealed class AddOutputQueueViewModel : IDisposable -{ - private readonly OutputExtension[] _extensions; - private readonly ReadOnlyObservableCollection _suggestion; - private readonly IDisposable _disposable1; - private readonly IDisposable _disposable2; - private readonly ReadOnlyObservableCollection _availableExtensions; - private FilePickerFileType[]? _cachedFileTypes; - - public AddOutputQueueViewModel() - { - _extensions = ExtensionProvider.Current.GetExtensions(); - - _disposable1 = EditorService.Current.TabItems - .ToObservableChangeSet, EditorTabItem>() - .Filter(x => !OutputService.Current.Items.Any(y => y.Context.TargetFile == x.FilePath.Value)) - .Transform(x => x.FilePath.Value) - .Bind(out _suggestion) - .Subscribe(); - - _disposable2 = _extensions.AsObservableChangeSet() - .Filter(SelectedFile.Select>( - f => f == null - ? _ => false - : ext => ext.IsSupported(f))) - .Bind(out _availableExtensions) - .Subscribe(); - - CanAdd = SelectedFile.Select(File.Exists) - .CombineLatest(SelectedExtension.Select(x => x != null)) - .Select(x => x.First && x.Second) - .ToReadOnlyReactivePropertySlim(); - } - - public ReactiveProperty SelectedFile { get; } = new(); - - public ReadOnlyObservableCollection Suggestion => _suggestion; - - public ReadOnlyReactivePropertySlim CanAdd { get; } - - public ReactiveProperty SelectedExtension { get; } = new(); - - public ReadOnlyObservableCollection AvailableExtensions => _availableExtensions; - - public void Add() - { - if (SelectedFile.Value != null - && SelectedExtension.Value != null) - { - OutputService.Current.AddItem(SelectedFile.Value, SelectedExtension.Value); - } - } - - public FilePickerFileType[] GetFilePickerFileTypes() - { - return _cachedFileTypes ??= _extensions - .Select(x => x.GetFilePickerFileType()) - .Where(x => x != null) - .ToArray(); - } - - public void Dispose() - { - _disposable1.Dispose(); - _disposable2.Dispose(); - } -} diff --git a/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml new file mode 100644 index 000000000..4383bdb94 --- /dev/null +++ b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml.cs b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml.cs new file mode 100644 index 000000000..3a2b668b8 --- /dev/null +++ b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml.cs @@ -0,0 +1,21 @@ +using Beutl.ViewModels.Dialogs; +using FluentAvalonia.UI.Controls; + +namespace Beutl.Views.Dialogs; + +public partial class AddOutputProfileDialog : ContentDialog +{ + public AddOutputProfileDialog() + { + InitializeComponent(); + } + + protected override Type StyleKeyOverride => typeof(ContentDialog); + + protected override void OnPrimaryButtonClick(ContentDialogButtonClickEventArgs args) + { + base.OnPrimaryButtonClick(args); + if (DataContext is not AddOutputProfileViewModel vm) return; + vm.Add(); + } +} From 4717e19878caf680044bbf0c3258b3aeb776ca8e Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Fri, 27 Dec 2024 01:52:48 +0900 Subject: [PATCH 02/10] feat: display output dialog using tabs --- src/Beutl/Pages/OutputDialog.axaml | 74 -------- src/Beutl/Pages/OutputDialog.axaml.cs | 108 ------------ src/Beutl/Services/EditorService.cs | 3 +- src/Beutl/Services/OutputService.cs | 92 +++++----- .../PrimitiveImpls/OutputPageExtension.cs | 39 ----- .../PrimitiveImpls/OutputTabExtension.cs | 57 ++++++ .../LoadPrimitiveExtensionTask.cs | 2 +- .../ViewModels/EncoderSettingsViewModel.cs | 2 +- src/Beutl/ViewModels/OutputPageViewModel.cs | 75 -------- src/Beutl/ViewModels/OutputViewModel.cs | 23 +-- .../ViewModels/Tools/OutputTabViewModel.cs | 88 ++++++++++ .../Views/Dialogs/OutputProgressDialog.axaml | 25 +++ .../Dialogs/OutputProgressDialog.axaml.cs | 21 +++ src/Beutl/Views/OutputView.axaml | 162 +++++++++--------- src/Beutl/Views/OutputView.axaml.cs | 11 ++ .../Views/Tools/OutputPropertiesEditor.axaml | 16 ++ .../Tools/OutputPropertiesEditor.axaml.cs | 55 ++++++ src/Beutl/Views/Tools/OutputTab.axaml | 49 ++++++ src/Beutl/Views/Tools/OutputTab.axaml.cs | 79 +++++++++ 19 files changed, 534 insertions(+), 447 deletions(-) delete mode 100644 src/Beutl/Pages/OutputDialog.axaml delete mode 100644 src/Beutl/Pages/OutputDialog.axaml.cs delete mode 100644 src/Beutl/Services/PrimitiveImpls/OutputPageExtension.cs create mode 100644 src/Beutl/Services/PrimitiveImpls/OutputTabExtension.cs delete mode 100644 src/Beutl/ViewModels/OutputPageViewModel.cs create mode 100644 src/Beutl/ViewModels/Tools/OutputTabViewModel.cs create mode 100644 src/Beutl/Views/Dialogs/OutputProgressDialog.axaml create mode 100644 src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs create mode 100644 src/Beutl/Views/Tools/OutputPropertiesEditor.axaml create mode 100644 src/Beutl/Views/Tools/OutputPropertiesEditor.axaml.cs create mode 100644 src/Beutl/Views/Tools/OutputTab.axaml create mode 100644 src/Beutl/Views/Tools/OutputTab.axaml.cs diff --git a/src/Beutl/Pages/OutputDialog.axaml b/src/Beutl/Pages/OutputDialog.axaml deleted file mode 100644 index a49df9f7b..000000000 --- a/src/Beutl/Pages/OutputDialog.axaml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Beutl/Pages/OutputDialog.axaml.cs b/src/Beutl/Pages/OutputDialog.axaml.cs deleted file mode 100644 index ad642745b..000000000 --- a/src/Beutl/Pages/OutputDialog.axaml.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Templates; -using Avalonia.Interactivity; -using Avalonia.Platform; -using Beutl.Services; -using Beutl.ViewModels; -using Beutl.ViewModels.Dialogs; -using FluentAvalonia.UI.Windowing; - -namespace Beutl.Pages; - -public partial class OutputDialog : AppWindow -{ - private readonly IDataTemplate _sharedDataTemplate = new _DataTemplate(); - - public OutputDialog() - { - InitializeComponent(); - if (OperatingSystem.IsWindows()) - { - TitleBar.ExtendsContentIntoTitleBar = true; - TitleBar.Height = 40; - } - else if (OperatingSystem.IsMacOS()) - { - Grid.Margin = new Thickness(8, 30, 0, 0); - ExtendClientAreaToDecorationsHint = true; - ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; - } - - contentControl.ContentTemplate = _sharedDataTemplate; -#if DEBUG - this.AttachDevTools(); -#endif - } - - protected override void OnOpened(EventArgs e) - { - base.OnOpened(e); - if (DataContext is OutputPageViewModel viewModel) - { - viewModel.Restore(); - } - } - - protected override void OnClosed(EventArgs e) - { - base.OnClosed(e); - if (DataContext is OutputPageViewModel viewModel) - { - viewModel.Save(); - } - } - - private async void OnAddClick(object? sender, RoutedEventArgs e) - { - if (DataContext is OutputPageViewModel viewModel) - { - var dialogViewModel = new AddOutputQueueViewModel(); - var dialog = new AddOutputQueueDialog { DataContext = dialogViewModel }; - - await dialog.ShowAsync(); - dialogViewModel.Dispose(); - - viewModel.Save(); - } - } - - private void OnRemoveClick(object? sender, RoutedEventArgs e) - { - if (DataContext is OutputPageViewModel viewModel) - { - viewModel.RemoveSelected(); - viewModel.Save(); - } - } - - private sealed class _DataTemplate : IDataTemplate - { - private readonly Dictionary _contextToViewType = []; - - public Control? Build(object? param) - { - if (param is OutputQueueItem item) - { - if (_contextToViewType.TryGetValue(item.Context.Extension, out Control? control)) - { - control.DataContext = item.Context; - return control; - } - else if (item.Context.Extension.TryCreateControl(item.Context.TargetFile, out control)) - { - _contextToViewType[item.Context.Extension] = control; - control.DataContext = item.Context; - return control; - } - } - - return null; - } - - public bool Match(object? data) - { - return data is OutputQueueItem; - } - } -} diff --git a/src/Beutl/Services/EditorService.cs b/src/Beutl/Services/EditorService.cs index e546e0eb5..f0211c10a 100644 --- a/src/Beutl/Services/EditorService.cs +++ b/src/Beutl/Services/EditorService.cs @@ -113,7 +113,8 @@ public void ActivateTabItem(string? file) if (ext?.TryCreateContext(file, out IEditorContext? context) == true) { - context.IsEnabled.Value = !OutputService.Current.Items.Any(x => x.Context.TargetFile == file && x.Context.IsEncoding.Value); + // TODO: エンコード中にファイルが変更される可能性 + // context.IsEnabled.Value = !OutputService.Current.Items.Any(x => x.Context.TargetFile == file && x.Context.IsEncoding.Value); var tabItem2 = new EditorTabItem(context) { IsSelected = diff --git a/src/Beutl/Services/OutputService.cs b/src/Beutl/Services/OutputService.cs index d8291d639..7bb27da51 100644 --- a/src/Beutl/Services/OutputService.cs +++ b/src/Beutl/Services/OutputService.cs @@ -1,18 +1,17 @@ using System.Text.Json; using System.Text.Json.Nodes; - using Beutl.Api.Services; using Beutl.Logging; - +using Beutl.Models; +using Beutl.ViewModels; using Microsoft.Extensions.Logging; - using Reactive.Bindings; namespace Beutl.Services; -public sealed class OutputQueueItem : IDisposable +public sealed class OutputProfileItem : IDisposable { - public OutputQueueItem(IOutputContext context) + public OutputProfileItem(IOutputContext context) { Context = context; Name = Path.GetFileName(context.TargetFile); @@ -48,7 +47,7 @@ public void Dispose() Context.Dispose(); } - public static JsonNode ToJson(OutputQueueItem item) + public static JsonNode ToJson(OutputProfileItem item) { var ctxJson = new JsonObject(); item.Context.WriteToJson(ctxJson); @@ -60,7 +59,7 @@ public static JsonNode ToJson(OutputQueueItem item) }; } - public static OutputQueueItem? FromJson(JsonNode json, ILogger logger) + public static OutputProfileItem? FromJson(JsonNode json, ILogger logger) { try { @@ -70,7 +69,8 @@ public static JsonNode ToJson(OutputQueueItem item) string extensionStr = obj["Extension"]!.AsValue().GetValue(); Type? extensionType = TypeFormat.ToType(extensionStr); ExtensionProvider provider = ExtensionProvider.Current; - OutputExtension? extension = Array.Find(provider.GetExtensions(), x => x.GetType() == extensionType); + OutputExtension? extension = Array.Find(provider.GetExtensions(), + x => x.GetType() == extensionType); string file = obj["File"]!.AsValue().GetValue(); @@ -80,7 +80,7 @@ public static JsonNode ToJson(OutputQueueItem item) && extension.TryCreateContext(file, out IOutputContext? context)) { context.ReadFromJson(contextJson.AsObject()); - return new OutputQueueItem(context); + return new OutputProfileItem(context); } else { @@ -95,19 +95,18 @@ public static JsonNode ToJson(OutputQueueItem item) } } -public sealed class OutputService +public sealed class OutputService(EditViewModel editViewModel) { - private readonly CoreList _items = []; - private readonly ReactivePropertySlim _selectedItem = new(); - private readonly string _filePath = Path.Combine(BeutlEnvironment.GetHomeDirectoryPath(), "outputlist.json"); + private readonly CoreList _items = []; + private readonly ReactivePropertySlim _selectedItem = new(); + private readonly string _filePath = Path.Combine( + Path.GetDirectoryName(editViewModel.Scene.FileName)!, Constants.BeutlFolder, "output-profile.json"); private readonly ILogger _logger = Log.CreateLogger(); private bool _isRestored; - public static OutputService Current { get; } = new(); - - public ICoreList Items => _items; + public ICoreList Items => _items; - public IReactiveProperty SelectedItem => _selectedItem; + public IReactiveProperty SelectedItem => _selectedItem; public void AddItem(string file, OutputExtension extension) { @@ -115,17 +114,18 @@ public void AddItem(string file, OutputExtension extension) { throw new Exception("Already added"); } + if (!extension.TryCreateContext(file, out IOutputContext? context)) { throw new Exception("Failed to create context"); } - var item = new OutputQueueItem(context); + var item = new OutputProfileItem(context); Items.Add(item); SelectedItem.Value = item; } - public OutputExtension[] GetExtensions(string file) + public static OutputExtension[] GetExtensions(string file) { return ExtensionProvider.Current .GetExtensions() @@ -134,44 +134,40 @@ public OutputExtension[] GetExtensions(string file) public void SaveItems() { - if (_isRestored) - { - var array = new JsonArray(); - foreach (OutputQueueItem item in _items.GetMarshal().Value) - { - JsonNode json = OutputQueueItem.ToJson(item); - array.Add(json); - } + if (!_isRestored) return; - using FileStream stream = File.Create(_filePath); - using var writer = new Utf8JsonWriter(stream); - array.WriteTo(writer); + var array = new JsonArray(); + foreach (OutputProfileItem item in _items.GetMarshal().Value) + { + JsonNode json = OutputProfileItem.ToJson(item); + array.Add(json); } + + using FileStream stream = File.Create(_filePath); + using var writer = new Utf8JsonWriter(stream); + array.WriteTo(writer); } public void RestoreItems() { _isRestored = true; - if (File.Exists(_filePath)) + if (!File.Exists(_filePath)) return; + + using FileStream stream = File.Open(_filePath, FileMode.Open); + var jsonNode = JsonNode.Parse(stream); + if (jsonNode is not JsonArray jsonArray) return; + + _items.Clear(); + _items.EnsureCapacity(jsonArray.Count); + + foreach (JsonNode? jsonItem in jsonArray) { - using FileStream stream = File.Open(_filePath, FileMode.Open); - var jsonNode = JsonNode.Parse(stream); - if (jsonNode is JsonArray jsonArray) + if (jsonItem == null) continue; + + var item = OutputProfileItem.FromJson(jsonItem, _logger); + if (item != null) { - _items.Clear(); - _items.EnsureCapacity(jsonArray.Count); - - foreach (JsonNode? jsonItem in jsonArray) - { - if (jsonItem != null) - { - var item = OutputQueueItem.FromJson(jsonItem, _logger); - if (item != null) - { - _items.Add(item); - } - } - } + _items.Add(item); } } } diff --git a/src/Beutl/Services/PrimitiveImpls/OutputPageExtension.cs b/src/Beutl/Services/PrimitiveImpls/OutputPageExtension.cs deleted file mode 100644 index f1a92699b..000000000 --- a/src/Beutl/Services/PrimitiveImpls/OutputPageExtension.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Avalonia.Controls; -using Beutl.Pages; -using Beutl.ViewModels; -using FluentAvalonia.UI.Controls; -using Symbol = FluentIcons.Common.Symbol; -using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; - -namespace Beutl.Services.PrimitiveImpls; - -[PrimitiveImpl] -public sealed class OutputPageExtension : PageExtension -{ - public static readonly OutputPageExtension Instance = new(); - - public override string Name => "OutputPage"; - - public override string DisplayName => Strings.Output; - - public override IPageContext CreateContext() - { - return new OutputPageViewModel(); - } - - public override Control CreateControl() - { - return new OutputDialog(); - } - - [Obsolete] - public override IconSource GetFilledIcon() - { - return new SymbolIconSource() { Symbol = Symbol.ArrowExportLtr, IsFilled = true }; - } - - public override IconSource GetRegularIcon() - { - return new SymbolIconSource() { Symbol = Symbol.ArrowExportLtr }; - } -} diff --git a/src/Beutl/Services/PrimitiveImpls/OutputTabExtension.cs b/src/Beutl/Services/PrimitiveImpls/OutputTabExtension.cs new file mode 100644 index 000000000..23fa3416b --- /dev/null +++ b/src/Beutl/Services/PrimitiveImpls/OutputTabExtension.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Beutl.ViewModels; +using Beutl.ViewModels.Tools; +using Beutl.Views.Tools; +using FluentAvalonia.UI.Controls; +using Symbol = FluentIcons.Common.Symbol; +using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; + +namespace Beutl.Services.PrimitiveImpls; + +[PrimitiveImpl] +public sealed class OutputTabExtension : ToolTabExtension +{ + public static readonly OutputTabExtension Instance = new(); + + public override string Name => "Output"; + + public override string DisplayName => Strings.Output; + + public override string? Header => Strings.Output; + + public override bool CanMultiple => false; + + public override IconSource GetIcon() + { + return new SymbolIconSource { Symbol = Symbol.ArrowExportLtr }; + } + + public override bool TryCreateContent(IEditorContext editorContext, [NotNullWhen(true)] out Control? control) + { + if (editorContext is EditViewModel) + { + control = new OutputTab(); + return true; + } + else + { + control = null; + return false; + } + } + + public override bool TryCreateContext(IEditorContext editorContext, [NotNullWhen(true)] out IToolContext? context) + { + if (editorContext is EditViewModel editViewModel) + { + context = new OutputTabViewModel(editViewModel); + return true; + } + else + { + context = null; + return false; + } + } +} diff --git a/src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs b/src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs index 2187a42e0..0e5e6c343 100644 --- a/src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs +++ b/src/Beutl/Services/StartupTasks/LoadPrimitiveExtensionTask.cs @@ -10,7 +10,7 @@ public sealed class LoadPrimitiveExtensionTask : StartupTask public static readonly Extension[] PrimitiveExtensions = [ ExtensionsPageExtension.Instance, - OutputPageExtension.Instance, + OutputTabExtension.Instance, SceneEditorExtension.Instance, SceneOutputExtension.Instance, SceneProjectItemExtension.Instance, diff --git a/src/Beutl/ViewModels/EncoderSettingsViewModel.cs b/src/Beutl/ViewModels/EncoderSettingsViewModel.cs index f64aff1f7..bfd3cc77c 100644 --- a/src/Beutl/ViewModels/EncoderSettingsViewModel.cs +++ b/src/Beutl/ViewModels/EncoderSettingsViewModel.cs @@ -59,7 +59,7 @@ private void InitializeCoreObject(MediaEncoderSettings obj, (foundItems, extension) = PropertyEditorService.MatchProperty(props); if (foundItems != null && extension != null) { - if (extension.TryCreateContextForSettings(foundItems, out IPropertyEditorContext? context)) + if (extension.TryCreateContext(foundItems, out IPropertyEditorContext? context)) { tempItems.Add(context); context.Accept(this); diff --git a/src/Beutl/ViewModels/OutputPageViewModel.cs b/src/Beutl/ViewModels/OutputPageViewModel.cs deleted file mode 100644 index 0f1bc56a6..000000000 --- a/src/Beutl/ViewModels/OutputPageViewModel.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Beutl.Logging; -using Beutl.Services; -using Beutl.Services.PrimitiveImpls; -using Beutl.ViewModels.ExtensionsPages; - -using Microsoft.Extensions.Logging; - -using Reactive.Bindings; - -namespace Beutl.ViewModels; - -public sealed class OutputPageViewModel : BasePageViewModel, IPageContext -{ - private readonly OutputService _outputService; - private readonly ILogger _logger = Log.CreateLogger(); - - public OutputPageViewModel() - { - _outputService = OutputService.Current; - CanRemove = SelectedItem - .SelectMany(x => x?.Context?.IsEncoding?.Not() ?? Observable.Return(false)) - .ToReadOnlyReactivePropertySlim(); - } - - public PageExtension Extension => OutputPageExtension.Instance; - - public string Header => Strings.Output; - - public ICoreList Items => _outputService.Items; - - public IReactiveProperty SelectedItem => _outputService.SelectedItem; - - public ReadOnlyReactivePropertySlim CanRemove { get; } - - public void AddItem(string file, OutputExtension extension) - { - try - { - _outputService.AddItem(file, extension); - } - catch (Exception e) - { - _logger.LogError(e, "An exception has occurred."); - - ErrorHandle(e); - } - } - - public void RemoveSelected() - { - if (SelectedItem.Value != null) - { - Items.Remove(SelectedItem.Value); - } - } - - public OutputExtension[] GetExtensions(string file) - { - return _outputService.GetExtensions(file); - } - - public void Save() - { - _outputService.SaveItems(); - } - - public void Restore() - { - _outputService.RestoreItems(); - } - - public override void Dispose() - { - } -} diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/OutputViewModel.cs index 1c27f399e..52fe0d6aa 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/OutputViewModel.cs @@ -87,8 +87,6 @@ public OutputViewModel(SceneFile model) public ReadOnlyObservableCollection Encoders => _encoders; - public ReactivePropertySlim IsEncodersExpanded { get; } = new(); - public ReadOnlyReactivePropertySlim CanEncode { get; } public ReadOnlyReactivePropertySlim Controller { get; } @@ -97,8 +95,6 @@ public OutputViewModel(SceneFile model) public ReadOnlyReactivePropertySlim AudioSettings { get; } - public ReactivePropertySlim ScrollOffset { get; } = new(); - public ReactiveProperty ProgressMax { get; } = new(); public ReactiveProperty ProgressValue { get; } = new(); @@ -147,7 +143,7 @@ static string[] ToPatterns(ControllableEncodingExtension encoder) .ToArray(); } - public async void StartEncode() + public async Task StartEncode() { try { @@ -256,8 +252,6 @@ public void WriteToJson(JsonObject json) json[nameof(SelectedEncoder)] = TypeFormat.ToString(SelectedEncoder.Value.GetType()); } - json[nameof(IsEncodersExpanded)] = IsEncodersExpanded.Value; - json[nameof(ScrollOffset)] = ScrollOffset.Value.ToString(); json[nameof(VideoSettings)] = Serialize(VideoSettings.Value?.Settings); json[nameof(AudioSettings)] = Serialize(AudioSettings.Value?.Settings); } @@ -294,21 +288,6 @@ void Deserialize(MediaEncoderSettings? settings, JsonObject json) SelectedEncoder.Value = encoder; } - if (json.TryGetPropertyValue(nameof(IsEncodersExpanded), out JsonNode? isExpandedNode) - && isExpandedNode is JsonValue isExpandedValue - && isExpandedValue.TryGetValue(out bool isExpanded)) - { - IsEncodersExpanded.Value = isExpanded; - } - - if (json.TryGetPropertyValue(nameof(ScrollOffset), out JsonNode? scrollOfstNode) - && scrollOfstNode is JsonValue scrollOfstValue - && scrollOfstValue.TryGetValue(out string? scrollOfstStr) - && Graphics.Vector.TryParse(scrollOfstStr, out Graphics.Vector vec)) - { - ScrollOffset.Value = new Avalonia.Vector(vec.X, vec.Y); - } - // 上のSelectedEncoder.Value = encoder;でnull以外が指定された場合、VideoSettings, AudioSettingsもnullじゃなくなる。 if (json.TryGetPropertyValue(nameof(VideoSettings), out JsonNode? videoNode) && videoNode is JsonObject videoObj) diff --git a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs new file mode 100644 index 000000000..710b081ce --- /dev/null +++ b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs @@ -0,0 +1,88 @@ +using System.Text.Json.Nodes; +using Beutl.Logging; +using Beutl.Services; +using Beutl.Services.PrimitiveImpls; +using Microsoft.Extensions.Logging; +using Reactive.Bindings; + +namespace Beutl.ViewModels.Tools; + +public class OutputTabViewModel : IToolContext +{ + private readonly OutputService _outputService; + private readonly ILogger _logger = Log.CreateLogger(); + + public OutputTabViewModel(EditViewModel editViewModel) + { + EditViewModel = editViewModel; + _outputService = new OutputService(editViewModel); + CanRemove = SelectedItem + .SelectMany(x => x?.Context?.IsEncoding?.Not() ?? Observable.Return(false)) + .ToReadOnlyReactivePropertySlim(); + } + + public EditViewModel EditViewModel { get; } + + public ToolTabExtension Extension => OutputTabExtension.Instance; + + public IReactiveProperty IsSelected { get; } = new ReactiveProperty(); + + public IReactiveProperty Placement { get; } + = new ReactiveProperty(ToolTabExtension.TabPlacement.RightUpperBottom); + + public IReactiveProperty DisplayMode { get; } + = new ReactiveProperty(); + + public string Header => Strings.Output; + + public ICoreList Items => _outputService.Items; + + public IReactiveProperty SelectedItem => _outputService.SelectedItem; + + public ReadOnlyReactivePropertySlim CanRemove { get; } + + public void AddItem(OutputExtension extension) + { + try + { + _outputService.AddItem(EditViewModel.Scene.FileName, extension); + } + catch (Exception e) + { + _logger.LogError(e, "An exception has occurred."); + e.Handle(); + } + } + + public void RemoveSelected() + { + if (SelectedItem.Value != null) + { + Items.Remove(SelectedItem.Value); + } + } + + public void Save() + { + _outputService.SaveItems(); + } + + public void Dispose() + { + } + + public void WriteToJson(JsonObject json) + { + _outputService.SaveItems(); + } + + public void ReadFromJson(JsonObject json) + { + _outputService.RestoreItems(); + } + + public object? GetService(Type serviceType) + { + return null; + } +} diff --git a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml new file mode 100644 index 000000000..2dfc78452 --- /dev/null +++ b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs new file mode 100644 index 000000000..bcf8a23e3 --- /dev/null +++ b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs @@ -0,0 +1,21 @@ +using Beutl.ViewModels; +using FluentAvalonia.UI.Controls; + +namespace Beutl.Views.Dialogs; + +public partial class OutputProgressDialog : ContentDialog +{ + public OutputProgressDialog() + { + InitializeComponent(); + } + + protected override Type StyleKeyOverride => typeof(ContentDialog); + + protected override void OnCloseButtonClick(ContentDialogButtonClickEventArgs args) + { + base.OnCloseButtonClick(args); + if (DataContext is not OutputViewModel vm) return; + vm.CancelEncode(); + } +} diff --git a/src/Beutl/Views/OutputView.axaml b/src/Beutl/Views/OutputView.axaml index 3aef0c23f..548832b89 100644 --- a/src/Beutl/Views/OutputView.axaml +++ b/src/Beutl/Views/OutputView.axaml @@ -3,98 +3,104 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:icons="using:FluentIcons.FluentAvalonia" - xmlns:pages="using:Beutl.Pages.SettingsPages" xmlns:lang="using:Beutl.Language" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:tools="using:Beutl.Views.Tools" xmlns:viewModel="using:Beutl.ViewModels" - Padding="16" d:DesignHeight="800" d:DesignWidth="600" x:CompileBindings="True" x:DataType="viewModel:OutputViewModel" mc:Ignorable="d"> - - - - - + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Tools/OutputTab.axaml.cs b/src/Beutl/Views/Tools/OutputTab.axaml.cs new file mode 100644 index 000000000..f541c202b --- /dev/null +++ b/src/Beutl/Views/Tools/OutputTab.axaml.cs @@ -0,0 +1,79 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Interactivity; +using Beutl.Pages; +using Beutl.Services; +using Beutl.ViewModels.Dialogs; +using Beutl.ViewModels.Tools; +using AddOutputProfileDialog = Beutl.Views.Dialogs.AddOutputProfileDialog; + +namespace Beutl.Views.Tools; + +public partial class OutputTab : UserControl +{ + private readonly IDataTemplate _sharedDataTemplate = new _DataTemplate(); + + public OutputTab() + { + InitializeComponent(); + contentControl.ContentTemplate = _sharedDataTemplate; + } + + private async void OnAddClick(object? sender, RoutedEventArgs e) + { + if (DataContext is not OutputTabViewModel viewModel) return; + + var ext = OutputService.GetExtensions(viewModel.EditViewModel.Scene.FileName); + if (ext.Length == 1) + { + viewModel.AddItem(ext[0]); + } + else + { + var dialogViewModel = new AddOutputProfileViewModel(viewModel); + var dialog = new AddOutputProfileDialog { DataContext = dialogViewModel }; + + await dialog.ShowAsync(); + } + + viewModel.Save(); + } + + private void OnRemoveClick(object? sender, RoutedEventArgs e) + { + if (DataContext is not OutputTabViewModel viewModel) return; + + viewModel.RemoveSelected(); + viewModel.Save(); + } + + private sealed class _DataTemplate : IDataTemplate + { + private readonly Dictionary _contextToViewType = []; + + public Control? Build(object? param) + { + if (param is OutputProfileItem item) + { + if (_contextToViewType.TryGetValue(item.Context.Extension, out Control? control)) + { + control.DataContext = item.Context; + return control; + } + else if (item.Context.Extension.TryCreateControl(item.Context.TargetFile, out control)) + { + _contextToViewType[item.Context.Extension] = control; + control.DataContext = item.Context; + return control; + } + } + + return null; + } + + public bool Match(object? data) + { + return data is OutputProfileItem; + } + } +} From 9a6051bb5049fad773b4fdbdb982d7fd2faaff2f Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Fri, 27 Dec 2024 23:57:04 +0900 Subject: [PATCH 03/10] feat: enable profile name modification --- src/Beutl.Extensibility/OutputExtension.cs | 2 ++ src/Beutl/Services/OutputService.cs | 6 +++--- src/Beutl/ViewModels/OutputViewModel.cs | 10 +++++++++ src/Beutl/Views/Tools/OutputTab.axaml | 24 +++++++++++++++------ src/Beutl/Views/Tools/OutputTab.axaml.cs | 25 +++++++++++++++++++++- 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/Beutl.Extensibility/OutputExtension.cs b/src/Beutl.Extensibility/OutputExtension.cs index 8ef5d1636..16bc6c766 100644 --- a/src/Beutl.Extensibility/OutputExtension.cs +++ b/src/Beutl.Extensibility/OutputExtension.cs @@ -15,6 +15,8 @@ public interface IOutputContext : IDisposable, IJsonSerializable string TargetFile { get; } + IReactiveProperty Name { get; } + IReadOnlyReactiveProperty IsIndeterminate { get; } IReadOnlyReactiveProperty IsEncoding { get; } diff --git a/src/Beutl/Services/OutputService.cs b/src/Beutl/Services/OutputService.cs index 7bb27da51..89917edb8 100644 --- a/src/Beutl/Services/OutputService.cs +++ b/src/Beutl/Services/OutputService.cs @@ -14,7 +14,6 @@ public sealed class OutputProfileItem : IDisposable public OutputProfileItem(IOutputContext context) { Context = context; - Name = Path.GetFileName(context.TargetFile); Context.Started += OnStarted; Context.Finished += OnFinished; @@ -22,8 +21,6 @@ public OutputProfileItem(IOutputContext context) public IOutputContext Context { get; } - public string Name { get; } - private void OnStarted(object? sender, EventArgs e) { if (EditorService.Current.TryGetTabItem(Context.TargetFile, out EditorTabItem? tabItem)) @@ -99,8 +96,10 @@ public sealed class OutputService(EditViewModel editViewModel) { private readonly CoreList _items = []; private readonly ReactivePropertySlim _selectedItem = new(); + private readonly string _filePath = Path.Combine( Path.GetDirectoryName(editViewModel.Scene.FileName)!, Constants.BeutlFolder, "output-profile.json"); + private readonly ILogger _logger = Log.CreateLogger(); private bool _isRestored; @@ -120,6 +119,7 @@ public void AddItem(string file, OutputExtension extension) throw new Exception("Failed to create context"); } + context.Name.Value = Items.Count == 0 ? "Default" : $"Profile {Items.Count}"; var item = new OutputProfileItem(context); Items.Add(item); SelectedItem.Value = item; diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/OutputViewModel.cs index 52fe0d6aa..785be1c28 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/OutputViewModel.cs @@ -81,6 +81,8 @@ public OutputViewModel(SceneFile model) public string TargetFile => Model.FileName; + public IReactiveProperty Name { get; } = new ReactiveProperty(""); + public ReactivePropertySlim DestinationFile { get; } = new(); public ReactivePropertySlim SelectedEncoder { get; } = new(); @@ -246,6 +248,7 @@ public void WriteToJson(JsonObject json) } } + json[nameof(Name)] = Name.Value; json[nameof(DestinationFile)] = DestinationFile.Value; if (SelectedEncoder.Value != null) { @@ -278,6 +281,13 @@ void Deserialize(MediaEncoderSettings? settings, JsonObject json) DestinationFile.Value = dstFile; } + if (json.TryGetPropertyValue(nameof(Name), out JsonNode? nameNode) + && nameNode is JsonValue nameValue + && nameValue.TryGetValue(out string? name)) + { + Name.Value = name; + } + if (json.TryGetPropertyValue(nameof(SelectedEncoder), out JsonNode? encoderNode) && encoderNode is JsonValue encoderValue && encoderValue.TryGetValue(out string? encoderStr) diff --git a/src/Beutl/Views/Tools/OutputTab.axaml b/src/Beutl/Views/Tools/OutputTab.axaml index 868e1a57a..7c7edbce2 100644 --- a/src/Beutl/Views/Tools/OutputTab.axaml +++ b/src/Beutl/Views/Tools/OutputTab.axaml @@ -8,6 +8,7 @@ xmlns:viewModels="using:Beutl.ViewModels.Tools" d:DesignHeight="450" d:DesignWidth="400" + x:CompileBindings="True" x:DataType="viewModels:OutputTabViewModel" mc:Ignorable="d"> @@ -21,7 +22,7 @@ SelectedItem="{Binding SelectedItem.Value}"> - + @@ -33,13 +34,22 @@ ToolTip.Tip="{x:Static lang:Strings.Add}"> - diff --git a/src/Beutl/Views/Tools/OutputTab.axaml.cs b/src/Beutl/Views/Tools/OutputTab.axaml.cs index f541c202b..b45097857 100644 --- a/src/Beutl/Views/Tools/OutputTab.axaml.cs +++ b/src/Beutl/Views/Tools/OutputTab.axaml.cs @@ -1,10 +1,10 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Interactivity; -using Beutl.Pages; using Beutl.Services; using Beutl.ViewModels.Dialogs; using Beutl.ViewModels.Tools; +using Beutl.Views.NodeTree; using AddOutputProfileDialog = Beutl.Views.Dialogs.AddOutputProfileDialog; namespace Beutl.Views.Tools; @@ -47,6 +47,29 @@ private void OnRemoveClick(object? sender, RoutedEventArgs e) viewModel.Save(); } + private void OnRenameClick(object? sender, RoutedEventArgs e) + { + if (DataContext is not OutputTabViewModel viewModel) return; + + var flyout = new RenameFlyout + { + Text = viewModel.SelectedItem.Value?.Context.Name.Value ?? string.Empty + }; + + flyout.Confirmed += OnNameConfirmed; + + flyout.ShowAt(MoreButton); + } + + private void OnNameConfirmed(object? sender, string? e) + { + if (DataContext is not OutputTabViewModel viewModel) return; + if (viewModel.SelectedItem.Value == null) return; + + viewModel.SelectedItem.Value.Context.Name.Value = e ?? ""; + viewModel.Save(); + } + private sealed class _DataTemplate : IDataTemplate { private readonly Dictionary _contextToViewType = []; From b845676a756927674a129c6280fee9478643a4e2 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Fri, 27 Dec 2024 23:57:42 +0900 Subject: [PATCH 04/10] feat: create a new profile if none exists --- src/Beutl/ViewModels/Tools/OutputTabViewModel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs index 710b081ce..89b43d604 100644 --- a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs @@ -19,6 +19,15 @@ public OutputTabViewModel(EditViewModel editViewModel) CanRemove = SelectedItem .SelectMany(x => x?.Context?.IsEncoding?.Not() ?? Observable.Return(false)) .ToReadOnlyReactivePropertySlim(); + + if (Items.Count == 0) + { + var ext = OutputService.GetExtensions(EditViewModel.Scene.FileName); + if (ext.Length == 1) + { + AddItem(ext[0]); + } + } } public EditViewModel EditViewModel { get; } From db486befc2c8fbd597859d74f3e63ac0ad435883 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Fri, 27 Dec 2024 23:58:13 +0900 Subject: [PATCH 05/10] feat: add CompileBindings configuration --- src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml | 7 ++++--- src/Beutl/Views/Dialogs/OutputProgressDialog.axaml | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml index 4383bdb94..cf0d7b056 100644 --- a/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml +++ b/src/Beutl/Views/Dialogs/AddOutputProfileDialog.axaml @@ -1,9 +1,8 @@ @@ -25,7 +26,7 @@ SelectedItem="{Binding SelectedExtension.Value}" SelectionMode="AlwaysSelected"> - + From 8704ed7076fc1177dfe819580819332e7bc56a85 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Sat, 28 Dec 2024 00:07:02 +0900 Subject: [PATCH 06/10] change: move OutputView to the Tools namespace --- src/Beutl/Services/PrimitiveImpls/SceneOutputExtension.cs | 5 ++--- src/Beutl/ViewModels/{ => Tools}/OutputViewModel.cs | 2 +- src/Beutl/Views/Dialogs/OutputProgressDialog.axaml | 3 ++- src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs | 1 + src/Beutl/Views/{ => Tools}/OutputView.axaml | 5 ++--- src/Beutl/Views/{ => Tools}/OutputView.axaml.cs | 3 ++- 6 files changed, 10 insertions(+), 9 deletions(-) rename src/Beutl/ViewModels/{ => Tools}/OutputViewModel.cs (99%) rename src/Beutl/Views/{ => Tools}/OutputView.axaml (96%) rename src/Beutl/Views/{ => Tools}/OutputView.axaml.cs (95%) diff --git a/src/Beutl/Services/PrimitiveImpls/SceneOutputExtension.cs b/src/Beutl/Services/PrimitiveImpls/SceneOutputExtension.cs index d021afc3c..f4b6ca6ce 100644 --- a/src/Beutl/Services/PrimitiveImpls/SceneOutputExtension.cs +++ b/src/Beutl/Services/PrimitiveImpls/SceneOutputExtension.cs @@ -4,9 +4,8 @@ using Avalonia.Platform.Storage; using Beutl.ProjectSystem; -using Beutl.ViewModels; -using Beutl.Views; - +using Beutl.ViewModels.Tools; +using Beutl.Views.Tools; using FluentAvalonia.UI.Controls; namespace Beutl.Services.PrimitiveImpls; diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/Tools/OutputViewModel.cs similarity index 99% rename from src/Beutl/ViewModels/OutputViewModel.cs rename to src/Beutl/ViewModels/Tools/OutputViewModel.cs index 785be1c28..60732128b 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/Tools/OutputViewModel.cs @@ -18,7 +18,7 @@ using Reactive.Bindings; using Reactive.Bindings.Extensions; -namespace Beutl.ViewModels; +namespace Beutl.ViewModels.Tools; public sealed class OutputViewModel : IOutputContext { diff --git a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml index e01d5abc1..b3fa94f76 100644 --- a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml +++ b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml @@ -4,13 +4,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:lang="using:Beutl.Language" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:tools="clr-namespace:Beutl.ViewModels.Tools" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="using:Beutl.ViewModels" Title="{x:Static lang:Strings.Output}" d:DesignHeight="450" d:DesignWidth="800" x:CompileBindings="True" - x:DataType="vm:OutputViewModel" + x:DataType="tools:OutputViewModel" CloseButtonText="{x:Static lang:Strings.Cancel}" mc:Ignorable="d"> diff --git a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs index bcf8a23e3..bd61b4a45 100644 --- a/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs +++ b/src/Beutl/Views/Dialogs/OutputProgressDialog.axaml.cs @@ -1,4 +1,5 @@ using Beutl.ViewModels; +using Beutl.ViewModels.Tools; using FluentAvalonia.UI.Controls; namespace Beutl.Views.Dialogs; diff --git a/src/Beutl/Views/OutputView.axaml b/src/Beutl/Views/Tools/OutputView.axaml similarity index 96% rename from src/Beutl/Views/OutputView.axaml rename to src/Beutl/Views/Tools/OutputView.axaml index 548832b89..321f5a47a 100644 --- a/src/Beutl/Views/OutputView.axaml +++ b/src/Beutl/Views/Tools/OutputView.axaml @@ -1,12 +1,11 @@ - Date: Sat, 28 Dec 2024 00:07:55 +0900 Subject: [PATCH 07/10] fix: correct default profile creation process --- .../ViewModels/Tools/OutputTabViewModel.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs index 89b43d604..32279692a 100644 --- a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs @@ -19,15 +19,6 @@ public OutputTabViewModel(EditViewModel editViewModel) CanRemove = SelectedItem .SelectMany(x => x?.Context?.IsEncoding?.Not() ?? Observable.Return(false)) .ToReadOnlyReactivePropertySlim(); - - if (Items.Count == 0) - { - var ext = OutputService.GetExtensions(EditViewModel.Scene.FileName); - if (ext.Length == 1) - { - AddItem(ext[0]); - } - } } public EditViewModel EditViewModel { get; } @@ -88,6 +79,19 @@ public void WriteToJson(JsonObject json) public void ReadFromJson(JsonObject json) { _outputService.RestoreItems(); + CreateDefaultProfile(); + SelectedItem.Value = Items.FirstOrDefault(); + } + + private void CreateDefaultProfile() + { + if (Items.Count != 0) return; + + var ext = OutputService.GetExtensions(EditViewModel.Scene.FileName); + if (ext.Length == 1) + { + AddItem(ext[0]); + } } public object? GetService(Type serviceType) From 9034d6a7fa0e851793ab89c1df4678968200a8c5 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Sat, 28 Dec 2024 00:09:43 +0900 Subject: [PATCH 08/10] remove: delete unnecessary comments --- src/Beutl/Services/EditorService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Beutl/Services/EditorService.cs b/src/Beutl/Services/EditorService.cs index f0211c10a..bcea3418e 100644 --- a/src/Beutl/Services/EditorService.cs +++ b/src/Beutl/Services/EditorService.cs @@ -113,8 +113,6 @@ public void ActivateTabItem(string? file) if (ext?.TryCreateContext(file, out IEditorContext? context) == true) { - // TODO: エンコード中にファイルが変更される可能性 - // context.IsEnabled.Value = !OutputService.Current.Items.Any(x => x.Context.TargetFile == file && x.Context.IsEncoding.Value); var tabItem2 = new EditorTabItem(context) { IsSelected = From f5d9420c76208478d1564fe45248f7498b9d56e9 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Sat, 28 Dec 2024 23:14:04 +0900 Subject: [PATCH 09/10] feat: set output tab to display by default --- src/Beutl/ViewModels/DockHostViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Beutl/ViewModels/DockHostViewModel.cs b/src/Beutl/ViewModels/DockHostViewModel.cs index 135663e54..81b837ac9 100644 --- a/src/Beutl/ViewModels/DockHostViewModel.cs +++ b/src/Beutl/ViewModels/DockHostViewModel.cs @@ -374,7 +374,10 @@ public void OpenDefaultTabs() { var tabs = new ToolTabExtension[] { - TimelineTabExtension.Instance, SourceOperatorsTabExtension.Instance, LibraryTabExtension.Instance + TimelineTabExtension.Instance, + OutputTabExtension.Instance, + SourceOperatorsTabExtension.Instance, + LibraryTabExtension.Instance, }; foreach (var ext in tabs) { From aae3c65e63eea0da4203fe0df61c194662ddc2b0 Mon Sep 17 00:00:00 2001 From: Yuto Terada Date: Sat, 28 Dec 2024 23:14:42 +0900 Subject: [PATCH 10/10] fix: ensure default output profile is created for new projects --- src/Beutl/ViewModels/Tools/OutputTabViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs index 32279692a..9201ce998 100644 --- a/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/OutputTabViewModel.cs @@ -19,6 +19,8 @@ public OutputTabViewModel(EditViewModel editViewModel) CanRemove = SelectedItem .SelectMany(x => x?.Context?.IsEncoding?.Not() ?? Observable.Return(false)) .ToReadOnlyReactivePropertySlim(); + CreateDefaultProfile(); + SelectedItem.Value = Items.FirstOrDefault(); } public EditViewModel EditViewModel { get; }