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.Specialized;
using System.Diagnostics;
using Avalonia;
using Avalonia.Animation;
@@ -76,24 +77,35 @@ public class SectionPanel : Panel
get => GetValue(ChildrenSourceProperty);
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
{
get => GetValue(ChildrenTemplateProperty);
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 bool _initialized = false;
private int _previousIndex = 0;
private bool _isChangingIndex = false;
public SectionPanel()
: base()
{
CurrentIndexProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) =>
{
CanScrollForward = Cyclic || CurrentIndex != Children.Count() - 1;
CanScrollBackward = Cyclic || CurrentIndex != 0;
if (panel != this)
return;
panel.CanScrollBackward = panel.Cyclic || panel.CurrentIndex != 0;
panel.CanScrollForward = panel.Cyclic || panel.CurrentIndex != panel.Children.Count() - 1;
_isChangingIndex = true;
panel.InvalidateArrange();
});
ChildrenSourceProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) =>
@@ -111,10 +123,11 @@ public class SectionPanel : Panel
else
controls = enumerable.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
.Select((vm, i) =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
control?.DataContext = vm;
control?.Name = $"Элемент {i}";
return control;
})
.OfType<Control>();
@@ -127,7 +140,6 @@ public class SectionPanel : Panel
_childIndeces.Clear();
foreach (var (i, child) in Children.Enumerate())
_childIndeces.Add(child, i);
_initialized = true;
}
}
panel.InvalidateArrange();
@@ -157,19 +169,19 @@ public class SectionPanel : Panel
controls = ea.NewItems.OfType<Control>();
else
controls = ea.NewItems.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
return control;
})
.OfType<Control>();
.Where(ChildrenTemplate.Match)
.Select((vm, i) =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = vm;
control?.Name = $"Элемент {i}";
return control;
})
.OfType<Control>();
foreach (Control control in controls)
Children.Insert(index++, control);
break;
}
@@ -232,10 +244,11 @@ public class SectionPanel : Panel
else
controls = ChildrenSource.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
.Select((vm, i) =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
control?.DataContext = vm;
control?.Name = $"Элемент {i}";
return control;
})
.OfType<Control>();
@@ -261,8 +274,6 @@ public class SectionPanel : Panel
return;
}
Console.WriteLine($"{e.NewSize} {e.PreviousSize}");
foreach (var (child, i) in _childIndeces)
{
var transform = child.RenderTransform as TranslateTransform;
@@ -271,7 +282,6 @@ public class SectionPanel : Panel
transform = new TranslateTransform();
child.RenderTransform = transform;
}
Console.WriteLine($"{i} {transform}");
if (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,
_ => 0
};
Console.WriteLine($"{i} {transform}");
}
base.OnSizeChanged(e);
}
@@ -300,18 +309,21 @@ public class SectionPanel : Panel
_initialized = true;
}
foreach (var (child, i) in _childIndeces)
{
var rect = Orientation switch
if (!_isChangingIndex)
foreach (var (child, i) in _childIndeces)
{
Orientation.Vertical => new Rect(0, i * finalSize.Height, finalSize.Width, finalSize.Height),
Orientation.Horizontal => new Rect(i * finalSize.Width, 0, finalSize.Width, finalSize.Height),
_ => default
};
child.Arrange(rect);
}
var rect = Orientation switch
{
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),
_ => default
};
child.Arrange(rect);
}
if (Children.Count() > 1)
Dispatcher.UIThread.Post(async () => _ = ApplyOffset(finalSize, true), DispatcherPriority.Render);
_isChangingIndex = false;
return finalSize;
}
@@ -333,6 +345,7 @@ public class SectionPanel : Panel
var newIndex = firstIndex - 1;
var (child, _) = _childIndeces.MaxBy(ch => ch.Value);
InstantTransform(child, -Children.Count(), size);
_childIndeces[child] = newIndex;
}
@@ -342,24 +355,23 @@ public class SectionPanel : Panel
var newIndex = lastIndex + 1;
var (child, _) = _childIndeces.MinBy(ch => ch.Value);
InstantTransform(child, Children.Count(), size);
_childIndeces[child] = newIndex;
}
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,
_ => 0
};
Orientation.Vertical => -indexSingleShift * size.Height,
Orientation.Horizontal => -indexSingleShift * size.Width,
_ => 0
};
foreach (var (i, child) in Children.Enumerate())
if (!animate || !EnableAnimation)
InstantTransform(child, offset);
else
animations.Add(AnimationTransform(child, offset, Math.Abs(indexShift)));
}
if (animations.Count() > 0)
await Task.WhenAll(animations);
@@ -380,12 +392,14 @@ public class SectionPanel : Panel
if (control.RenderTransformOrigin != RelativePoint.TopLeft)
control.RenderTransformOrigin = RelativePoint.TopLeft;
if (Orientation is Orientation.Vertical)
transform.Y += offset * (size is null ? 1 : size.Value.Height);
else
transform.X += offset * (size is null ? 1 : size.Value.Width);
_ = Orientation switch
{
Orientation.Vertical => transform.Y += offset * (size is null ? 1 : size.Value.Height),
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;
if (transform is null)
@@ -407,13 +421,18 @@ public class SectionPanel : Panel
Cue = new Cue(1),
Setters =
{
Orientation is Orientation.Vertical ?
new Setter(TranslateTransform.YProperty, transform.Y + offset) :
new Setter(TranslateTransform.XProperty, transform.X + offset)
Orientation switch
{
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 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;
@@ -43,6 +45,7 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
[ObservableProperty] private int _imagesSectionCurrentIndex = 0;
[ObservableProperty] private bool _imagesSectionCanScrollForward = false;
[ObservableProperty] private bool _imagesSectionCanScrollBackward = false;
[ObservableProperty] private bool _imagesSectionIsScrolling = true;
public AmineContentSeparatorCalibrationViewModel(ILogger<AmineContentSeparatorCalibrationViewModel> logger, AmineContentCalibrationContext context, IlluminatorService illuminatorService, CameraService cameraService, ImageStorageService imageStorageService)
{
@@ -121,7 +124,7 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
var deletedIsNewRecord = IsNewRecord;
IsNewRecord = true;
CreateRecord();
if (!deletedIsNewRecord)
{
Records.Remove(deletedRecord);
@@ -155,14 +158,7 @@ public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase
IsNewRecord = false;
}
[RelayCommand]
private void SwitchMainSection()
{
MainSectionsCurrentIndex = MainSectionsCurrentIndex == 0 ? 1 : 0;
SwitchMainSectionButtonText = MainSectionsCurrentIndex == 0 ?
"К изображениям" :
"К информации";
}
[RelayCommand] private void SwitchMainSection() => MainSectionsCurrentIndex = MainSectionsCurrentIndex == 0 ? 1 : 0;
[RelayCommand]
private void CaptureImage()
@@ -146,8 +146,11 @@
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CanScrollBackward="{Binding MainSectionCanScrollBackward}"
CanScrollForward="{Binding MainSectionCanScrollForward}"
ClipToBounds="True"
CurrentIndex="{Binding MainSectionsCurrentIndex}"
IsScrolling="{Binding MainSectionIsScrolling}"
Orientation="Vertical">
<!-- Информация -->
@@ -223,8 +226,16 @@
VerticalContentAlignment="Center"
Classes="vertical"
Command="{Binding ImagesSectionPreviousCommand}"
Content="◀"
IsEnabled="{Binding ImagesSectionCanScrollBackward}" />
Content="◀">
<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
Grid.Column="1"
@@ -234,6 +245,7 @@
ClipToBounds="True"
CurrentIndex="{Binding ImagesSectionCurrentIndex}"
Cyclic="True"
IsScrolling="{Binding ImagesSectionIsScrolling}"
Orientation="Horizontal">
<core:SectionPanel.ChildrenTemplate>
@@ -343,8 +355,16 @@
VerticalContentAlignment="Center"
Classes="vertical"
Command="{Binding ImagesSectionNextCommand}"
Content="▶"
IsEnabled="{Binding ImagesSectionCanScrollForward}" />
Content="▶">
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="ImagesSectionCanScrollForward" />
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesSectionIsScrolling" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</Grid>
@@ -418,7 +438,34 @@
Grid.Column="2"
HorizontalAlignment="Stretch"
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>