fix: SectionPanel scrolling

This commit is contained in:
2026-03-03 14:35:26 +03:00
parent b4c8a92c63
commit 660ce43834
3 changed files with 124 additions and 62 deletions
+66 -47
View File
@@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics;
using Avalonia; using Avalonia;
using Avalonia.Animation; using Avalonia.Animation;
@@ -76,24 +77,35 @@ public class SectionPanel : Panel
get => GetValue(ChildrenSourceProperty); get => GetValue(ChildrenSourceProperty);
set => SetValue(ChildrenSourceProperty, value); set => SetValue(ChildrenSourceProperty, value);
} }
public static readonly StyledProperty<IDataTemplate?> ChildrenTemplateProperty = AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ChildrenTemplate), null); public static readonly StyledProperty<IDataTemplate?> ChildrenTemplateProperty = AvaloniaProperty.Register<SectionPanel, IDataTemplate?>(nameof(ChildrenTemplate), null);
public IDataTemplate? ChildrenTemplate public IDataTemplate? ChildrenTemplate
{ {
get => GetValue(ChildrenTemplateProperty); get => GetValue(ChildrenTemplateProperty);
set => SetValue(ChildrenTemplateProperty, value); set => SetValue(ChildrenTemplateProperty, value);
} }
public static readonly StyledProperty<bool> IsScrollingProperty = AvaloniaProperty.Register<SectionPanel, bool>(nameof(IsScrolling), false, defaultBindingMode: Avalonia.Data.BindingMode.OneWayToSource);
public bool IsScrolling
{
get => GetValue(IsScrollingProperty);
set => SetValue(IsScrollingProperty, value);
}
private readonly Dictionary<Control, int> _childIndeces = new Dictionary<Control, int>(); private readonly Dictionary<Control, int> _childIndeces = new Dictionary<Control, int>();
private bool _initialized = false; private bool _initialized = false;
private int _previousIndex = 0; private int _previousIndex = 0;
private bool _isChangingIndex = false;
public SectionPanel() public SectionPanel()
: base() : base()
{ {
CurrentIndexProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) => CurrentIndexProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) =>
{ {
CanScrollForward = Cyclic || CurrentIndex != Children.Count() - 1; if (panel != this)
CanScrollBackward = Cyclic || CurrentIndex != 0; return;
panel.CanScrollBackward = panel.Cyclic || panel.CurrentIndex != 0;
panel.CanScrollForward = panel.Cyclic || panel.CurrentIndex != panel.Children.Count() - 1;
_isChangingIndex = true;
panel.InvalidateArrange(); panel.InvalidateArrange();
}); });
ChildrenSourceProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) => ChildrenSourceProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) =>
@@ -111,10 +123,11 @@ public class SectionPanel : Panel
else else
controls = enumerable.Cast<object?>() controls = enumerable.Cast<object?>()
.Where(ChildrenTemplate.Match) .Where(ChildrenTemplate.Match)
.Select(i => .Select((vm, i) =>
{ {
var control = ChildrenTemplate.Build(i); var control = ChildrenTemplate.Build(i);
control?.DataContext = i; control?.DataContext = vm;
control?.Name = $"Элемент {i}";
return control; return control;
}) })
.OfType<Control>(); .OfType<Control>();
@@ -127,7 +140,6 @@ public class SectionPanel : Panel
_childIndeces.Clear(); _childIndeces.Clear();
foreach (var (i, child) in Children.Enumerate()) foreach (var (i, child) in Children.Enumerate())
_childIndeces.Add(child, i); _childIndeces.Add(child, i);
_initialized = true;
} }
} }
panel.InvalidateArrange(); panel.InvalidateArrange();
@@ -157,19 +169,19 @@ public class SectionPanel : Panel
controls = ea.NewItems.OfType<Control>(); controls = ea.NewItems.OfType<Control>();
else else
controls = ea.NewItems.Cast<object?>() controls = ea.NewItems.Cast<object?>()
.Where(ChildrenTemplate.Match) .Where(ChildrenTemplate.Match)
.Select(i => .Select((vm, i) =>
{ {
var control = ChildrenTemplate.Build(i); var control = ChildrenTemplate.Build(i);
control?.DataContext = i; control?.DataContext = vm;
return control; control?.Name = $"Элемент {i}";
}) return control;
.OfType<Control>(); })
.OfType<Control>();
foreach (Control control in controls) foreach (Control control in controls)
Children.Insert(index++, control); Children.Insert(index++, control);
break; break;
} }
@@ -232,10 +244,11 @@ public class SectionPanel : Panel
else else
controls = ChildrenSource.Cast<object?>() controls = ChildrenSource.Cast<object?>()
.Where(ChildrenTemplate.Match) .Where(ChildrenTemplate.Match)
.Select(i => .Select((vm, i) =>
{ {
var control = ChildrenTemplate.Build(i); var control = ChildrenTemplate.Build(i);
control?.DataContext = i; control?.DataContext = vm;
control?.Name = $"Элемент {i}";
return control; return control;
}) })
.OfType<Control>(); .OfType<Control>();
@@ -261,8 +274,6 @@ public class SectionPanel : Panel
return; return;
} }
Console.WriteLine($"{e.NewSize} {e.PreviousSize}");
foreach (var (child, i) in _childIndeces) foreach (var (child, i) in _childIndeces)
{ {
var transform = child.RenderTransform as TranslateTransform; var transform = child.RenderTransform as TranslateTransform;
@@ -271,7 +282,6 @@ public class SectionPanel : Panel
transform = new TranslateTransform(); transform = new TranslateTransform();
child.RenderTransform = transform; child.RenderTransform = transform;
} }
Console.WriteLine($"{i} {transform}");
if (child.RenderTransformOrigin != RelativePoint.TopLeft) if (child.RenderTransformOrigin != RelativePoint.TopLeft)
child.RenderTransformOrigin = RelativePoint.TopLeft; child.RenderTransformOrigin = RelativePoint.TopLeft;
@@ -281,7 +291,6 @@ public class SectionPanel : Panel
Orientation.Horizontal => transform.X += transform.X == 0 ? 0 : e.PreviousSize.Width - e.NewSize.Width, Orientation.Horizontal => transform.X += transform.X == 0 ? 0 : e.PreviousSize.Width - e.NewSize.Width,
_ => 0 _ => 0
}; };
Console.WriteLine($"{i} {transform}");
} }
base.OnSizeChanged(e); base.OnSizeChanged(e);
} }
@@ -300,19 +309,22 @@ public class SectionPanel : Panel
_initialized = true; _initialized = true;
} }
foreach (var (child, i) in _childIndeces) if (!_isChangingIndex)
{ foreach (var (child, i) in _childIndeces)
var rect = Orientation switch
{ {
Orientation.Vertical => new Rect(0, i * finalSize.Height, finalSize.Width, finalSize.Height), var rect = Orientation switch
Orientation.Horizontal => new Rect(i * finalSize.Width, 0, finalSize.Width, finalSize.Height), {
_ => default Orientation.Vertical => new Rect(0, (i - _previousIndex) * finalSize.Height, finalSize.Width, finalSize.Height),
}; Orientation.Horizontal => new Rect((i - _previousIndex) * finalSize.Width, 0, finalSize.Width, finalSize.Height),
child.Arrange(rect); _ => default
} };
child.Arrange(rect);
}
if (Children.Count() > 1) if (Children.Count() > 1)
Dispatcher.UIThread.Post(async () => _ = ApplyOffset(finalSize, true), DispatcherPriority.Render); Dispatcher.UIThread.Post(async () => _ = ApplyOffset(finalSize, true), DispatcherPriority.Render);
_isChangingIndex = false;
return finalSize; return finalSize;
} }
@@ -333,6 +345,7 @@ public class SectionPanel : Panel
var newIndex = firstIndex - 1; var newIndex = firstIndex - 1;
var (child, _) = _childIndeces.MaxBy(ch => ch.Value); var (child, _) = _childIndeces.MaxBy(ch => ch.Value);
InstantTransform(child, -Children.Count(), size); InstantTransform(child, -Children.Count(), size);
_childIndeces[child] = newIndex; _childIndeces[child] = newIndex;
} }
@@ -342,24 +355,23 @@ public class SectionPanel : Panel
var newIndex = lastIndex + 1; var newIndex = lastIndex + 1;
var (child, _) = _childIndeces.MinBy(ch => ch.Value); var (child, _) = _childIndeces.MinBy(ch => ch.Value);
InstantTransform(child, Children.Count(), size); InstantTransform(child, Children.Count(), size);
_childIndeces[child] = newIndex; _childIndeces[child] = newIndex;
} }
List<Task> animations = new List<Task>(); List<Task> animations = new List<Task>();
foreach (var (i, child) in Children.Enumerate()) double offset = Orientation switch
{ {
double offset = Orientation switch Orientation.Vertical => -indexSingleShift * size.Height,
{ Orientation.Horizontal => -indexSingleShift * size.Width,
Orientation.Vertical => -indexSingleShift * size.Height, _ => 0
Orientation.Horizontal => -indexSingleShift * size.Width, };
_ => 0
};
foreach (var (i, child) in Children.Enumerate())
if (!animate || !EnableAnimation) if (!animate || !EnableAnimation)
InstantTransform(child, offset); InstantTransform(child, offset);
else else
animations.Add(AnimationTransform(child, offset, Math.Abs(indexShift))); animations.Add(AnimationTransform(child, offset, Math.Abs(indexShift)));
}
if (animations.Count() > 0) if (animations.Count() > 0)
await Task.WhenAll(animations); await Task.WhenAll(animations);
@@ -380,12 +392,14 @@ public class SectionPanel : Panel
if (control.RenderTransformOrigin != RelativePoint.TopLeft) if (control.RenderTransformOrigin != RelativePoint.TopLeft)
control.RenderTransformOrigin = RelativePoint.TopLeft; control.RenderTransformOrigin = RelativePoint.TopLeft;
if (Orientation is Orientation.Vertical) _ = Orientation switch
transform.Y += offset * (size is null ? 1 : size.Value.Height); {
else Orientation.Vertical => transform.Y += offset * (size is null ? 1 : size.Value.Height),
transform.X += offset * (size is null ? 1 : size.Value.Width); Orientation.Horizontal => transform.X += offset * (size is null ? 1 : size.Value.Width),
_ => throw new UnreachableException(),
};
} }
private Task AnimationTransform(Control control, double offset, double speedRatio) private Task AnimationTransform(Control control, double offset, double speedRatio, Size? size = null)
{ {
var transform = control.RenderTransform as TranslateTransform; var transform = control.RenderTransform as TranslateTransform;
if (transform is null) if (transform is null)
@@ -407,13 +421,18 @@ public class SectionPanel : Panel
Cue = new Cue(1), Cue = new Cue(1),
Setters = Setters =
{ {
Orientation is Orientation.Vertical ? Orientation switch
new Setter(TranslateTransform.YProperty, transform.Y + offset) : {
new Setter(TranslateTransform.XProperty, transform.X + offset) Orientation.Vertical => new Setter(TranslateTransform.YProperty, transform.Y + offset * (size is null ? 1 : size.Value.Height)),
Orientation.Horizontal => new Setter(TranslateTransform.XProperty, transform.X + offset * (size is null ? 1 : size.Value.Width)),
_ => throw new UnreachableException(),
}
} }
} }
} }
}; };
return animation.RunAsync(control);
IsScrolling = true;
return animation.RunAsync(control).ContinueWith(_ => Dispatcher.UIThread.Invoke(() => IsScrolling = false));
} }
} }
@@ -33,7 +33,9 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
[ObservableProperty] private bool _isNewRecord = false; [ObservableProperty] private bool _isNewRecord = false;
[ObservableProperty] private int _mainSectionsCurrentIndex = 0; [ObservableProperty] private int _mainSectionsCurrentIndex = 0;
[ObservableProperty] private string _switchMainSectionButtonText = "К изображениям"; [ObservableProperty] private bool _mainSectionCanScrollBackward = false;
[ObservableProperty] private bool _mainSectionCanScrollForward = true;
[ObservableProperty] private bool _mainSectionIsScrolling = true;
[ObservableProperty] private Task? _captureImagesTask = null; [ObservableProperty] private Task? _captureImagesTask = null;
@@ -43,6 +45,7 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
[ObservableProperty] private int _imagesSectionCurrentIndex = 0; [ObservableProperty] private int _imagesSectionCurrentIndex = 0;
[ObservableProperty] private bool _imagesSectionCanScrollForward = false; [ObservableProperty] private bool _imagesSectionCanScrollForward = false;
[ObservableProperty] private bool _imagesSectionCanScrollBackward = false; [ObservableProperty] private bool _imagesSectionCanScrollBackward = false;
[ObservableProperty] private bool _imagesSectionIsScrolling = true;
public AmineContentSeparatorCalibrationViewModel(ILogger<AmineContentSeparatorCalibrationViewModel> logger, AmineContentCalibrationContext context, IlluminatorService illuminatorService, CameraService cameraService, ImageStorageService imageStorageService) public AmineContentSeparatorCalibrationViewModel(ILogger<AmineContentSeparatorCalibrationViewModel> logger, AmineContentCalibrationContext context, IlluminatorService illuminatorService, CameraService cameraService, ImageStorageService imageStorageService)
{ {
@@ -155,14 +158,7 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
IsNewRecord = false; IsNewRecord = false;
} }
[RelayCommand] [RelayCommand] private void SwitchMainSection() => MainSectionsCurrentIndex = MainSectionsCurrentIndex == 0 ? 1 : 0;
private void SwitchMainSection()
{
MainSectionsCurrentIndex = MainSectionsCurrentIndex == 0 ? 1 : 0;
SwitchMainSectionButtonText = MainSectionsCurrentIndex == 0 ?
"К изображениям" :
"К информации";
}
[RelayCommand] [RelayCommand]
private void CaptureImage() private void CaptureImage()
@@ -146,8 +146,11 @@
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
CanScrollBackward="{Binding MainSectionCanScrollBackward}"
CanScrollForward="{Binding MainSectionCanScrollForward}"
ClipToBounds="True" ClipToBounds="True"
CurrentIndex="{Binding MainSectionsCurrentIndex}" CurrentIndex="{Binding MainSectionsCurrentIndex}"
IsScrolling="{Binding MainSectionIsScrolling}"
Orientation="Vertical"> Orientation="Vertical">
<!-- Информация --> <!-- Информация -->
@@ -223,8 +226,16 @@
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="vertical" Classes="vertical"
Command="{Binding ImagesSectionPreviousCommand}" Command="{Binding ImagesSectionPreviousCommand}"
Content="◀" Content="◀">
IsEnabled="{Binding ImagesSectionCanScrollBackward}" />
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="ImagesSectionCanScrollBackward" />
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesSectionIsScrolling" />
</MultiBinding>
</Button.IsEnabled>
</Button>
<core:SectionPanel <core:SectionPanel
Grid.Column="1" Grid.Column="1"
@@ -234,6 +245,7 @@
ClipToBounds="True" ClipToBounds="True"
CurrentIndex="{Binding ImagesSectionCurrentIndex}" CurrentIndex="{Binding ImagesSectionCurrentIndex}"
Cyclic="True" Cyclic="True"
IsScrolling="{Binding ImagesSectionIsScrolling}"
Orientation="Horizontal"> Orientation="Horizontal">
<core:SectionPanel.ChildrenTemplate> <core:SectionPanel.ChildrenTemplate>
@@ -343,8 +355,16 @@
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="vertical" Classes="vertical"
Command="{Binding ImagesSectionNextCommand}" Command="{Binding ImagesSectionNextCommand}"
Content="▶" Content="▶">
IsEnabled="{Binding ImagesSectionCanScrollForward}" />
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="ImagesSectionCanScrollForward" />
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesSectionIsScrolling" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</Grid> </Grid>
@@ -418,7 +438,34 @@
Grid.Column="2" Grid.Column="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{Binding SwitchMainSectionCommand}" Command="{Binding SwitchMainSectionCommand}"
Content="{Binding SwitchMainSectionButtonText}" /> Content="К информации"
IsVisible="{Binding MainSectionsCurrentIndex, Converter={StaticResource EqualsConverter}, ConverterParameter=1}">
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="MainSectionCanScrollBackward" />
<Binding Converter="{x:Static BoolConverters.Not}" Path="MainSectionIsScrolling" />
</MultiBinding>
</Button.IsEnabled>
</Button>
<Button
Grid.Row="1"
Grid.Column="2"
HorizontalAlignment="Stretch"
Command="{Binding SwitchMainSectionCommand}"
Content="К изображениям"
IsVisible="{Binding MainSectionsCurrentIndex, Converter={StaticResource EqualsConverter}, ConverterParameter=0}">
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="MainSectionCanScrollForward" />
<Binding Converter="{x:Static BoolConverters.Not}" Path="MainSectionIsScrolling" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</Grid> </Grid>