Anpassung am Sitzplan

This commit is contained in:
2026-08-19 11:23:54 +02:00
parent f868b3a259
commit 1731e5088e
2 changed files with 91 additions and 21 deletions
@@ -60,8 +60,8 @@
HorizontalAlignment="Center"/> HorizontalAlignment="Center"/>
</StackPanel> </StackPanel>
<Grid RowDefinitions="Auto,*" IsVisible="{Binding HasSelectedPlan}" Margin="24"> <Grid RowDefinitions="Auto,*" ColumnDefinitions="*,220" IsVisible="{Binding HasSelectedPlan}" Margin="24">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,18"> <Grid Grid.Row="0" Grid.ColumnSpan="2" ColumnDefinitions="*,Auto" Margin="0,0,0,18">
<StackPanel Spacing="3"> <StackPanel Spacing="3">
<TextBlock Text="{Binding PlanTitle}" FontSize="22" FontWeight="SemiBold"/> <TextBlock Text="{Binding PlanTitle}" FontSize="22" FontWeight="SemiBold"/>
<TextBlock Text="{Binding PlanSubtitle}" Opacity="0.65"/> <TextBlock Text="{Binding PlanSubtitle}" Opacity="0.65"/>
@@ -73,8 +73,13 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" <ScrollViewer x:Name="RoomScrollViewer" Grid.Row="1" Grid.Column="0"
VerticalScrollBarVisibility="Auto"> Margin="0,0,16,0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
DragDrop.AllowDrop="True"
DragDrop.DragOver="OnRoomDragOver"
DragDrop.DragLeave="OnRoomDragLeave">
<StackPanel Spacing="14" HorizontalAlignment="Center"> <StackPanel Spacing="14" HorizontalAlignment="Center">
<Border Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" <Border Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}"
CornerRadius="6" Padding="36,8" HorizontalAlignment="Center"> CornerRadius="6" Padding="36,8" HorizontalAlignment="Center">
@@ -113,37 +118,45 @@
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</StackPanel>
</ScrollViewer>
<Border DragDrop.AllowDrop="True" DragDrop.DragOver="OnUnassignedDragOver" <!-- Die Schülerliste hat einen eigenen Scrollbereich und bleibt dadurch auch bei
DragDrop.Drop="OnUnassignedDrop" großen Räumen neben den aktuell sichtbaren Sitzplätzen erreichbar. -->
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}" <Border Grid.Row="1" Grid.Column="1"
BorderThickness="1" CornerRadius="7" Padding="12" Margin="5,8"> DragDrop.AllowDrop="True" DragDrop.DragOver="OnUnassignedDragOver"
<StackPanel Spacing="8"> DragDrop.Drop="OnUnassignedDrop"
<TextBlock Text="NICHT ZUGEORDNET" FontSize="10" FontWeight="Bold" Opacity="0.5"/> BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
<TextBlock Text="Schüler hierher ziehen, um einen Platz zu leeren." BorderThickness="1" CornerRadius="7" Padding="12">
FontSize="11" Opacity="0.5"/> <Grid RowDefinitions="Auto,Auto,*">
<TextBlock Grid.Row="0" Text="NICHT ZUGEORDNET" FontSize="10"
FontWeight="Bold" Opacity="0.5"/>
<TextBlock Grid.Row="1" Margin="0,5,0,10"
Text="Schüler auf einen Platz ziehen. Belegte Plätze hierher ziehen, um sie zu leeren."
TextWrapping="Wrap" FontSize="11" Opacity="0.5"/>
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Spacing="6">
<ItemsControl ItemsSource="{Binding UnassignedStudents}"> <ItemsControl ItemsSource="{Binding UnassignedStudents}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:StudentSeatOption"> <DataTemplate x:DataType="vm:StudentSeatOption">
<Border Background="{DynamicResource SystemAccentColorLight2}" CornerRadius="5" <Border Background="{DynamicResource SystemAccentColorLight2}" CornerRadius="5"
Padding="9,5" Margin="0,0,6,6" Padding="9,7"
PointerPressed="OnDragSourcePressed" PointerPressed="OnDragSourcePressed"
PointerMoved="OnDragSourceMoved" PointerMoved="OnDragSourceMoved"
PointerReleased="OnDragSourceReleased"> PointerReleased="OnDragSourceReleased">
<TextBlock Text="{Binding DisplayName}" FontSize="12"/> <TextBlock Text="{Binding DisplayName}" FontSize="12" TextTrimming="CharacterEllipsis"/>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<TextBlock Text="Alle Schüler sind zugeordnet." Classes="emptyhint" <TextBlock Text="Alle Schüler sind zugeordnet." Classes="emptyhint"
TextWrapping="Wrap"
IsVisible="{Binding !UnassignedStudents.Count}"/> IsVisible="{Binding !UnassignedStudents.Count}"/>
</StackPanel> </StackPanel>
</Border> </ScrollViewer>
</StackPanel> </Grid>
</ScrollViewer> </Border>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
@@ -10,6 +10,9 @@ namespace LehrerApp.Desktop.Views.Groups;
public partial class SeatingPlanTabView : UserControl public partial class SeatingPlanTabView : UserControl
{ {
private const double AutoScrollEdgeSize = 72;
private const double AutoScrollStep = 14;
private object? _dragCandidate; private object? _dragCandidate;
private PointerPressedEventArgs? _dragTrigger; private PointerPressedEventArgs? _dragTrigger;
private Avalonia.Point _pressPosition; private Avalonia.Point _pressPosition;
@@ -18,8 +21,15 @@ public partial class SeatingPlanTabView : UserControl
private SeatCellViewModel? _pendingDropTarget; private SeatCellViewModel? _pendingDropTarget;
private bool _pendingClearSeat; private bool _pendingClearSeat;
private DateTime _ignoreTapUntil; private DateTime _ignoreTapUntil;
private readonly DispatcherTimer _autoScrollTimer;
private Avalonia.Vector _autoScrollDirection;
public SeatingPlanTabView() => InitializeComponent(); public SeatingPlanTabView()
{
InitializeComponent();
_autoScrollTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(30) };
_autoScrollTimer.Tick += OnAutoScrollTick;
}
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
@@ -69,6 +79,7 @@ public partial class SeatingPlanTabView : UserControl
_draggedStudent = null; _draggedStudent = null;
_pendingDropTarget = null; _pendingDropTarget = null;
_pendingClearSeat = false; _pendingClearSeat = false;
StopAutoScroll();
_ignoreTapUntil = DateTime.UtcNow.AddMilliseconds(250); _ignoreTapUntil = DateTime.UtcNow.AddMilliseconds(250);
// Never mutate an ItemsControl while Avalonia is still processing its native // Never mutate an ItemsControl while Avalonia is still processing its native
@@ -107,6 +118,7 @@ public partial class SeatingPlanTabView : UserControl
private void OnSeatDrop(object? sender, DragEventArgs e) private void OnSeatDrop(object? sender, DragEventArgs e)
{ {
StopAutoScroll();
if (sender is not Border { DataContext: SeatCellViewModel target }) return; if (sender is not Border { DataContext: SeatCellViewModel target }) return;
target.IsDropTarget = false; target.IsDropTarget = false;
if (!CanDropOn(target)) if (!CanDropOn(target))
@@ -128,6 +140,7 @@ public partial class SeatingPlanTabView : UserControl
private void OnUnassignedDrop(object? sender, DragEventArgs e) private void OnUnassignedDrop(object? sender, DragEventArgs e)
{ {
StopAutoScroll();
if (_draggedSeat is not null) if (_draggedSeat is not null)
{ {
_pendingDropTarget = null; _pendingDropTarget = null;
@@ -136,6 +149,50 @@ public partial class SeatingPlanTabView : UserControl
} }
} }
private void OnRoomDragOver(object? sender, DragEventArgs e)
{
if (_draggedSeat is null && _draggedStudent is null)
{
StopAutoScroll();
return;
}
var position = e.GetPosition(RoomScrollViewer);
_autoScrollDirection = new Avalonia.Vector(
GetAutoScrollDirection(position.X, RoomScrollViewer.Bounds.Width),
GetAutoScrollDirection(position.Y, RoomScrollViewer.Bounds.Height));
if (_autoScrollDirection == default)
StopAutoScroll();
else if (!_autoScrollTimer.IsEnabled)
_autoScrollTimer.Start();
}
private void OnRoomDragLeave(object? sender, DragEventArgs e) => StopAutoScroll();
private void OnAutoScrollTick(object? sender, EventArgs e)
{
var maxX = Math.Max(0, RoomScrollViewer.Extent.Width - RoomScrollViewer.Viewport.Width);
var maxY = Math.Max(0, RoomScrollViewer.Extent.Height - RoomScrollViewer.Viewport.Height);
var offset = RoomScrollViewer.Offset;
RoomScrollViewer.Offset = new Avalonia.Vector(
Math.Clamp(offset.X + _autoScrollDirection.X * AutoScrollStep, 0, maxX),
Math.Clamp(offset.Y + _autoScrollDirection.Y * AutoScrollStep, 0, maxY));
}
private static double GetAutoScrollDirection(double position, double viewportSize)
{
if (position < AutoScrollEdgeSize) return -1;
if (position > viewportSize - AutoScrollEdgeSize) return 1;
return 0;
}
private void StopAutoScroll()
{
_autoScrollDirection = default;
_autoScrollTimer.Stop();
}
private async void OnSeatTapped(object? sender, TappedEventArgs e) private async void OnSeatTapped(object? sender, TappedEventArgs e)
{ {
if (DateTime.UtcNow < _ignoreTapUntil || sender is not Border { DataContext: SeatCellViewModel seat } if (DateTime.UtcNow < _ignoreTapUntil || sender is not Border { DataContext: SeatCellViewModel seat }