diff --git a/src/Beutl.Language/Strings.Designer.cs b/src/Beutl.Language/Strings.Designer.cs
index 785654270..22624d4e7 100644
--- a/src/Beutl.Language/Strings.Designer.cs
+++ b/src/Beutl.Language/Strings.Designer.cs
@@ -2240,5 +2240,11 @@ public static string ClipTransparentArea {
return ResourceManager.GetString("ClipTransparentArea", resourceCulture);
}
}
+
+ public static string EnableElement {
+ get {
+ return ResourceManager.GetString("EnableElement", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Beutl.Language/Strings.ja.resx b/src/Beutl.Language/Strings.ja.resx
index 6a5e4d608..e1d16f218 100644
--- a/src/Beutl.Language/Strings.ja.resx
+++ b/src/Beutl.Language/Strings.ja.resx
@@ -1216,4 +1216,7 @@ b-editorがダウンロードURLを管理します。
透明部分をクリップ
+
+ 要素を有効化
+
diff --git a/src/Beutl.Language/Strings.resx b/src/Beutl.Language/Strings.resx
index 8b31f2e06..ee9d54de5 100644
--- a/src/Beutl.Language/Strings.resx
+++ b/src/Beutl.Language/Strings.resx
@@ -1216,4 +1216,7 @@ and b-editor maintains the download URL.
Clip transparent area
+
+ Enable element
+
diff --git a/src/Beutl/ViewModels/ElementViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs
index f629893d8..c3583cbde 100644
--- a/src/Beutl/ViewModels/ElementViewModel.cs
+++ b/src/Beutl/ViewModels/ElementViewModel.cs
@@ -121,11 +121,9 @@ public ElementViewModel(Element element, TimelineViewModel timeline)
{
LayerHeaderViewModel? newLH = Timeline.LayerHeaders.FirstOrDefault(i => i.Number.Value == number);
- if (LayerHeader.Value != null)
- LayerHeader.Value.ItemsCount.Value--;
+ LayerHeader.Value?.ElementRemoved(this);
- if (newLH != null)
- newLH.ItemsCount.Value++;
+ newLH?.ElementAdded(this);
LayerHeader.Value = newLH;
})
.AddTo(_disposables);
diff --git a/src/Beutl/ViewModels/LayerHeaderViewModel.cs b/src/Beutl/ViewModels/LayerHeaderViewModel.cs
index 96dfdeeb7..5303e8ca7 100644
--- a/src/Beutl/ViewModels/LayerHeaderViewModel.cs
+++ b/src/Beutl/ViewModels/LayerHeaderViewModel.cs
@@ -1,12 +1,9 @@
using System.Collections.Specialized;
using System.Text.Json.Nodes;
-
using Avalonia.Media;
-
using Beutl.Commands;
using Beutl.ProjectSystem;
using Beutl.Reactive;
-
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
@@ -15,6 +12,9 @@ namespace Beutl.ViewModels;
public sealed class LayerHeaderViewModel : IDisposable, IJsonSerializable
{
private readonly CompositeDisposable _disposables = [];
+ private readonly List _elements = [];
+ private IDisposable? _elementsSubscription;
+ private bool _skipSubscription;
public LayerHeaderViewModel(int num, TimelineViewModel timeline)
{
@@ -26,30 +26,40 @@ public LayerHeaderViewModel(int num, TimelineViewModel timeline)
.ToReadOnlyReactivePropertySlim()
.DisposeWith(_disposables);
- IsEnabled.Skip(1).Subscribe(b =>
- {
- CommandRecorder recorder = Timeline.EditorContext.CommandRecorder;
- Timeline.Scene.Children.Where(i => i.ZIndex == Number.Value && i.IsEnabled != b)
- .Select(item => RecordableCommands.Edit(item, Element.IsEnabledProperty, b).WithStoables([item]))
- .ToArray()
- .ToCommand()
- .DoAndRecord(recorder);
- }).DisposeWith(_disposables);
+ SwitchEnabledCommand = new ReactiveCommand()
+ .WithSubscribe(() =>
+ {
+ CommandRecorder recorder = Timeline.EditorContext.CommandRecorder;
+ try
+ {
+ _skipSubscription = true;
+ IsEnabled.Value = !IsEnabled.Value;
+ Timeline.Scene.Children.Where(i => i.ZIndex == Number.Value && i.IsEnabled != IsEnabled.Value)
+ .Select(item => RecordableCommands.Edit(item, Element.IsEnabledProperty, IsEnabled.Value).WithStoables([item]))
+ .ToArray()
+ .ToCommand()
+ .DoAndRecord(recorder);
+ }
+ finally
+ {
+ _skipSubscription = false;
+ }
+ });
Height.Subscribe(_ => Timeline.RaiseLayerHeightChanged(this)).DisposeWith(_disposables);
Inlines.ForEachItem(
- (idx, x) =>
- {
- Height.Value += FrameNumberHelper.LayerHeight;
- x.Index.Value = idx;
- },
- (_, x) =>
- {
- Height.Value -= FrameNumberHelper.LayerHeight;
- x.Index.Value = -1;
- },
- () => { })
+ (idx, x) =>
+ {
+ Height.Value += FrameNumberHelper.LayerHeight;
+ x.Index.Value = idx;
+ },
+ (_, x) =>
+ {
+ Height.Value -= FrameNumberHelper.LayerHeight;
+ x.Index.Value = -1;
+ },
+ () => { })
.DisposeWith(_disposables);
Inlines.CollectionChangedAsObservable()
@@ -118,6 +128,8 @@ void OnRemoved()
public CoreList Inlines { get; } = new() { ResetBehavior = ResetBehavior.Remove };
+ public ReactiveCommand SwitchEnabledCommand { get; }
+
public void AnimationRequest(int layerNum, bool affectModel = true)
{
if (affectModel)
@@ -127,6 +139,37 @@ public void AnimationRequest(int layerNum, bool affectModel = true)
PosY.Value = 0;
}
+ public void ElementAdded(ElementViewModel element)
+ {
+ ItemsCount.Value++;
+ _elements.Add(element);
+ BuildSubscription();
+ }
+
+ public void ElementRemoved(ElementViewModel element)
+ {
+ ItemsCount.Value--;
+ _elements.Remove(element);
+ BuildSubscription();
+ }
+
+ private void BuildSubscription()
+ {
+ _elementsSubscription?.Dispose();
+ _elementsSubscription = null;
+ if (_elements.Count == 0)
+ {
+ IsEnabled.Value = true;
+ return;
+ }
+
+ _elementsSubscription = _elements.Select(obj => obj.IsEnabled.Select(b => (bool?)b))
+ .Aggregate((x, y) => x.CombineLatest(y)
+ .Select(t => t.First == t.Second ? t.First : null))
+ .Where(b => b.HasValue && !_skipSubscription)
+ .Subscribe(b => IsEnabled.Value = b!.Value);
+ }
+
public void Dispose()
{
_disposables.Dispose();
diff --git a/src/Beutl/Views/ElementView.axaml b/src/Beutl/Views/ElementView.axaml
index 1f5efae57..050f85eee 100644
--- a/src/Beutl/Views/ElementView.axaml
+++ b/src/Beutl/Views/ElementView.axaml
@@ -19,7 +19,6 @@
x:DataType="vm:ElementViewModel"
ClipToBounds="True"
Focusable="True"
- IsEnabled="{CompiledBinding IsEnabled.Value}"
mc:Ignorable="d">
+
+
+
@@ -38,6 +47,15 @@
+
+
+
+
+
diff --git a/src/Beutl/Views/ElementView.axaml.cs b/src/Beutl/Views/ElementView.axaml.cs
index a2ce807bd..98dba65c4 100644
--- a/src/Beutl/Views/ElementView.axaml.cs
+++ b/src/Beutl/Views/ElementView.axaml.cs
@@ -8,16 +8,12 @@
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.Xaml.Interactivity;
-
using Beutl.Commands;
using Beutl.ProjectSystem;
using Beutl.ViewModels;
using Beutl.ViewModels.NodeTree;
-
using FluentAvalonia.UI.Controls;
-
using Reactive.Bindings.Extensions;
-
using Setter = Avalonia.Styling.Setter;
namespace Beutl.Views;
@@ -51,37 +47,35 @@ public ElementView()
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
- if (DataContext is ElementViewModel viewModel)
+ if (DataContext is not ElementViewModel viewModel) return;
+
+ if (e.Key == Key.F2)
{
- if (e.Key == Key.F2)
- {
- Rename_Click(null, null!);
- e.Handled = true;
- return;
- }
- else if (e.Key == Key.LeftCtrl)
- {
- _resizeBehavior?.OnLeftCtrlPressed(e);
- return;
- }
+ Rename_Click(null, null!);
+ e.Handled = true;
+ return;
+ }
+ else if (e.Key == Key.LeftCtrl)
+ {
+ _resizeBehavior?.OnLeftCtrlPressed(e);
+ return;
+ }
- // KeyBindingsは変更してはならない。
- foreach (KeyBinding binding in viewModel.KeyBindings)
- {
- if (e.Handled)
- break;
- binding.TryHandle(e);
- }
+ // KeyBindingsは変更してはならない。
+ foreach (KeyBinding binding in viewModel.KeyBindings)
+ {
+ if (e.Handled)
+ break;
+ binding.TryHandle(e);
}
}
private void OnContextFlyoutOpening(object? sender, EventArgs e)
{
- if (DataContext is ElementViewModel viewModel)
- {
- change2OriginalLength.IsEnabled = viewModel.HasOriginalLength();
- splitByCurrent.IsEnabled = viewModel.Model.Range.Contains(viewModel.Timeline.EditorContext.CurrentTime.Value);
- }
+ if (DataContext is not ElementViewModel viewModel) return;
+
+ change2OriginalLength.IsEnabled = viewModel.HasOriginalLength();
+ splitByCurrent.IsEnabled = viewModel.Model.Range.Contains(viewModel.Timeline.EditorContext.CurrentTime.Value);
}
private void OnDataContextDetached(ElementViewModel obj)
@@ -97,58 +91,8 @@ private void OnDataContextAttached(ElementViewModel obj)
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
- var animation1 = new Avalonia.Animation.Animation
- {
- Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
- Duration = TimeSpan.FromSeconds(0.25),
- FillMode = FillMode.Forward,
- Children =
- {
- new KeyFrame()
- {
- Cue = new Cue(0),
- Setters =
- {
- new Setter(MarginProperty, border.Margin),
- new Setter(WidthProperty, border.Width),
- }
- },
- new KeyFrame()
- {
- Cue = new Cue(1),
- Setters =
- {
- new Setter(MarginProperty, args.BorderMargin),
- new Setter(WidthProperty, args.Width)
- }
- }
- }
- };
- var animation2 = new Avalonia.Animation.Animation
- {
- Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
- Duration = TimeSpan.FromSeconds(0.25),
- FillMode = FillMode.Forward,
- Children =
- {
- new KeyFrame()
- {
- Cue = new Cue(0),
- Setters =
- {
- new Setter(MarginProperty, obj.Margin.Value)
- }
- },
- new KeyFrame()
- {
- Cue = new Cue(1),
- Setters =
- {
- new Setter(MarginProperty, args.Margin)
- }
- }
- }
- };
+ var animation1 = new Avalonia.Animation.Animation { Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0), Duration = TimeSpan.FromSeconds(0.25), FillMode = FillMode.Forward, Children = { new KeyFrame() { Cue = new Cue(0), Setters = { new Setter(MarginProperty, border.Margin), new Setter(WidthProperty, border.Width), } }, new KeyFrame() { Cue = new Cue(1), Setters = { new Setter(MarginProperty, args.BorderMargin), new Setter(WidthProperty, args.Width) } } } };
+ var animation2 = new Avalonia.Animation.Animation { Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0), Duration = TimeSpan.FromSeconds(0.25), FillMode = FillMode.Forward, Children = { new KeyFrame() { Cue = new Cue(0), Setters = { new Setter(MarginProperty, obj.Margin.Value) } }, new KeyFrame() { Cue = new Cue(1), Setters = { new Setter(MarginProperty, args.Margin) } } } };
Task task1 = animation1.RunAsync(border, token);
Task task2 = animation2.RunAsync(this, token);
@@ -157,11 +101,6 @@ await Dispatcher.UIThread.InvokeAsync(async () =>
};
obj.GetClickedTime = () => _pointerPosition;
- obj.Model.GetObservable(Element.IsEnabledProperty)
- .ObserveOnUIDispatcher()
- .Subscribe(b => border.Opacity = b ? 1 : 0.5)
- .DisposeWith(_disposables);
-
obj.IsSelected
.ObserveOnUIDispatcher()
.Subscribe(v => ZIndex = v ? 5 : 0)
@@ -206,6 +145,15 @@ private void UseNodeClick(object? sender, RoutedEventArgs e)
.DoAndRecord(recorder);
}
+ private void EnableElementClick(object? sender, RoutedEventArgs e)
+ {
+ Element model = ViewModel.Model;
+ CommandRecorder recorder = ViewModel.Timeline.EditorContext.CommandRecorder;
+ RecordableCommands.Edit(model, Element.IsEnabledProperty, !model.IsEnabled)
+ .WithStoables([model])
+ .DoAndRecord(recorder);
+ }
+
private void OnTextBoxLostFocus(object? sender, RoutedEventArgs e)
{
textBlock.IsVisible = true;
@@ -222,28 +170,27 @@ private void Rename_Click(object? sender, RoutedEventArgs e)
private void ChangeColor_Click(object? sender, RoutedEventArgs e)
{
- if (DataContext is ElementViewModel viewModel)
+ if (DataContext is not ElementViewModel viewModel) return;
+
+ // ContextMenuから開いているので、閉じるのを待つ
+ s_colorPickerFlyout ??= new ColorPickerFlyout();
+ s_colorPickerFlyout.ColorPicker.Color = viewModel.Color.Value;
+ s_colorPickerFlyout.ColorPicker.IsAlphaEnabled = false;
+ s_colorPickerFlyout.ColorPicker.UseColorPalette = true;
+ s_colorPickerFlyout.ColorPicker.IsCompact = true;
+ s_colorPickerFlyout.ColorPicker.IsMoreButtonVisible = true;
+ s_colorPickerFlyout.Placement = PlacementMode.Top;
+
+ if (this.TryFindResource("PaletteColors", out object? colors)
+ && colors is IEnumerable tcolors)
{
- // ContextMenuから開いているので、閉じるのを待つ
- s_colorPickerFlyout ??= new ColorPickerFlyout();
- s_colorPickerFlyout.ColorPicker.Color = viewModel.Color.Value;
- s_colorPickerFlyout.ColorPicker.IsAlphaEnabled = false;
- s_colorPickerFlyout.ColorPicker.UseColorPalette = true;
- s_colorPickerFlyout.ColorPicker.IsCompact = true;
- s_colorPickerFlyout.ColorPicker.IsMoreButtonVisible = true;
- s_colorPickerFlyout.Placement = PlacementMode.Top;
-
- if (this.TryFindResource("PaletteColors", out object? colors)
- && colors is IEnumerable tcolors)
- {
- s_colorPickerFlyout.ColorPicker.CustomPaletteColors = tcolors;
- }
+ s_colorPickerFlyout.ColorPicker.CustomPaletteColors = tcolors;
+ }
- s_colorPickerFlyout.Confirmed += OnColorPickerFlyoutConfirmed;
- s_colorPickerFlyout.Closed += OnColorPickerFlyoutClosed;
+ s_colorPickerFlyout.Confirmed += OnColorPickerFlyoutConfirmed;
+ s_colorPickerFlyout.Closed += OnColorPickerFlyoutClosed;
- s_colorPickerFlyout.ShowAt(border);
- }
+ s_colorPickerFlyout.ShowAt(border);
}
private void OnColorPickerFlyoutClosed(object? sender, EventArgs e)
@@ -325,13 +272,12 @@ public void OnLeftCtrlPressed(KeyEventArgs e)
protected override void OnAttached()
{
base.OnAttached();
- if (AssociatedObject != null)
- {
- AssociatedObject.AddHandler(PointerMovedEvent, OnPointerMoved);
- AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
- AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
- AssociatedObject.border.AddHandler(PointerMovedEvent, OnBorderPointerMoved);
- }
+ if (AssociatedObject == null) return;
+
+ AssociatedObject.AddHandler(PointerMovedEvent, OnPointerMoved);
+ AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
+ AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
+ AssociatedObject.border.AddHandler(PointerMovedEvent, OnBorderPointerMoved);
}
protected override void OnDetaching()
@@ -398,11 +344,11 @@ private void OnPointerMoved(object? sender, PointerEventArgs e)
private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e)
{
- if (AssociatedObject is { _timeline: { }, ViewModel: { } viewModel } view)
+ if (AssociatedObject is { _timeline: not null, ViewModel: { } viewModel } view)
{
PointerPoint point = e.GetCurrentPoint(view.border);
if (point.Properties.IsLeftButtonPressed && e.KeyModifiers is KeyModifiers.None or KeyModifiers.Alt
- && view.Cursor != Cursors.Arrow && view.Cursor is { })
+ && view.Cursor != Cursors.Arrow && view.Cursor is not null)
{
_before = viewModel.Model.GetBefore(viewModel.Model.ZIndex, viewModel.Model.Start);
_after = viewModel.Model.GetAfter(viewModel.Model.ZIndex, viewModel.Model.Range.End);
@@ -483,29 +429,26 @@ private sealed class _MoveBehavior : Behavior
protected override void OnAttached()
{
base.OnAttached();
- if (AssociatedObject != null)
- {
- AssociatedObject.AddHandler(PointerMovedEvent, OnPointerMoved);
- AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
- AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
- }
+ if (AssociatedObject == null) return;
+
+ AssociatedObject.AddHandler(PointerMovedEvent, OnPointerMoved);
+ AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
+ AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
}
protected override void OnDetaching()
{
base.OnDetaching();
- if (AssociatedObject != null)
- {
- AssociatedObject.RemoveHandler(PointerMovedEvent, OnPointerMoved);
- AssociatedObject.border.RemoveHandler(PointerPressedEvent, OnBorderPointerPressed);
- AssociatedObject.border.RemoveHandler(PointerReleasedEvent, OnBorderPointerReleased);
- }
+ if (AssociatedObject == null) return;
+
+ AssociatedObject.RemoveHandler(PointerMovedEvent, OnPointerMoved);
+ AssociatedObject.border.RemoveHandler(PointerPressedEvent, OnBorderPointerPressed);
+ AssociatedObject.border.RemoveHandler(PointerReleasedEvent, OnBorderPointerReleased);
}
private void OnPointerMoved(object? sender, PointerEventArgs e)
{
- if (AssociatedObject is { ViewModel: { } viewModel } view
- && view._timeline is { } timeline && _pressed)
+ if (AssociatedObject is { ViewModel: { } viewModel, _timeline: { } timeline } view && _pressed)
{
Point point = e.GetPosition(view);
float scale = viewModel.Timeline.Options.Value.Scale;
@@ -601,39 +544,36 @@ private sealed class _SelectBehavior : Behavior
protected override void OnAttached()
{
base.OnAttached();
- if (AssociatedObject != null)
- {
- AssociatedObject.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
- AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
- AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
- }
+ if (AssociatedObject == null) return;
+
+ AssociatedObject.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
+ AssociatedObject.border.AddHandler(PointerPressedEvent, OnBorderPointerPressed);
+ AssociatedObject.border.AddHandler(PointerReleasedEvent, OnBorderPointerReleased);
}
protected override void OnDetaching()
{
base.OnDetaching();
- if (AssociatedObject != null)
- {
- AssociatedObject.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
- AssociatedObject.border.RemoveHandler(PointerPressedEvent, OnBorderPointerPressed);
- AssociatedObject.border.RemoveHandler(PointerReleasedEvent, OnBorderPointerReleased);
- }
+ if (AssociatedObject == null) return;
+
+ AssociatedObject.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
+ AssociatedObject.border.RemoveHandler(PointerPressedEvent, OnBorderPointerPressed);
+ AssociatedObject.border.RemoveHandler(PointerReleasedEvent, OnBorderPointerReleased);
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
- if (AssociatedObject is { } obj)
+ if (AssociatedObject is not { } obj) return;
+
+ if (!obj.textBox.IsFocused)
{
- if (!obj.textBox.IsFocused)
- {
- obj.Focus();
- }
+ obj.Focus();
}
}
private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e)
{
- if (AssociatedObject is { _timeline: { } } obj)
+ if (AssociatedObject is { _timeline.ViewModel: not null } obj)
{
PointerPoint point = e.GetCurrentPoint(obj.border);
if (point.Properties.IsLeftButtonPressed)
@@ -657,11 +597,9 @@ private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e)
{
Thickness margin = obj.ViewModel.Margin.Value;
Thickness borderMargin = obj.ViewModel.BorderMargin.Value;
- _snapshot = new(borderMargin.Left, margin.Top, 0, 0);
+ _snapshot = new Thickness(borderMargin.Left, margin.Top, 0, 0);
_pressedWithModifier = true;
}
-
- obj.border.Opacity = 0.8;
}
}
}
@@ -669,22 +607,22 @@ private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e)
private void OnBorderPointerReleased(object? sender, PointerReleasedEventArgs e)
{
- if (AssociatedObject is { _timeline: { } } obj)
+ if (AssociatedObject is { _timeline: not null } obj)
{
if (_pressedWithModifier)
{
Thickness margin = obj.ViewModel.Margin.Value;
Thickness borderMargin = obj.ViewModel.BorderMargin.Value;
+ // ReSharper disable CompareOfFloatsByEqualityOperator
if (borderMargin.Left == _snapshot.Left
&& margin.Top == _snapshot.Top)
{
obj.ViewModel.IsSelected.Value = !obj.ViewModel.IsSelected.Value;
}
+ // ReSharper restore CompareOfFloatsByEqualityOperator
_pressedWithModifier = false;
}
-
- obj.border.Opacity = 1;
}
}
}
diff --git a/src/Beutl/Views/LayerHeader.axaml b/src/Beutl/Views/LayerHeader.axaml
index 58b141c01..5b3466c12 100644
--- a/src/Beutl/Views/LayerHeader.axaml
+++ b/src/Beutl/Views/LayerHeader.axaml
@@ -57,7 +57,8 @@