Compare commits

3 Commits
qwen ... master

Author SHA1 Message Date
a58d0b8f02 Добавить .gitea/workflows/test.yml
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m16s
2026-04-14 18:36:29 +03:00
74b63a8802 Добавить .gitea/workflow/test.yml 2026-04-14 18:35:59 +03:00
fd34dc6db1 - add: sort visuals 2026-04-12 19:40:01 +03:00
9 changed files with 912 additions and 9 deletions

19
.gitea/workflow/test.yml Normal file
View File

@@ -0,0 +1,19 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."

19
.gitea/workflows/test.yml Normal file
View File

@@ -0,0 +1,19 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -2,8 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InternalSortMethods"
x:Class="InternalSortMethods.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
RequestedThemeVariant="Dark">
<Application.DataTemplates>
<local:ViewLocator/>
@@ -11,5 +10,34 @@
<Application.Styles>
<FluentTheme />
<Style Selector="Window">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#0F0F1A" Offset="0"/>
<GradientStop Color="#1A1A2E" Offset="0.5"/>
<GradientStop Color="#16213E" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<Style Selector="ComboBox">
<Setter Property="Background" Value="#20FFFFFF"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#40FFFFFF"/>
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="MinHeight" Value="44"/>
</Style>
<Style Selector="ComboBox:pointerover /template/ Border#NormalRectangle">
<Setter Property="Background" Value="#30FFFFFF"/>
<Setter Property="BorderBrush" Value="#60FFFFFF"/>
</Style>
<Style Selector="Slider">
<Setter Property="MinHeight" Value="44"/>
</Style>
</Application.Styles>
</Application>

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
namespace InternalSortMethods.Controls;
public class ArrayVisualizer : Control
{
public static readonly StyledProperty<ObservableCollection<Models.ArrayItemModel>?> ItemsProperty =
AvaloniaProperty.Register<ArrayVisualizer, ObservableCollection<Models.ArrayItemModel>?>(nameof(Items));
public ObservableCollection<Models.ArrayItemModel>? Items
{
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
static ArrayVisualizer()
{
ItemsProperty.Changed.AddClassHandler<ArrayVisualizer>((x, e) => x.OnItemsChanged(e));
}
public ArrayVisualizer()
{
ClipToBounds = true;
}
private void OnItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ObservableCollection<Models.ArrayItemModel> oldCollection)
{
oldCollection.CollectionChanged -= OnCollectionChanged;
foreach (var item in oldCollection)
item.PropertyChanged -= OnItemPropertyChanged;
}
if (e.NewValue is ObservableCollection<Models.ArrayItemModel> newCollection)
{
newCollection.CollectionChanged += OnCollectionChanged;
foreach (var item in newCollection)
item.PropertyChanged += OnItemPropertyChanged;
}
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Models.ArrayItemModel item in e.NewItems)
item.PropertyChanged += OnItemPropertyChanged;
}
if (e.OldItems != null)
{
foreach (Models.ArrayItemModel item in e.OldItems)
item.PropertyChanged -= OnItemPropertyChanged;
}
InvalidateVisual();
}
private void OnItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs? e)
{
InvalidateVisual();
}
public override void Render(DrawingContext context)
{
if (Items == null || Items.Count == 0 || Bounds.Width <= 0 || Bounds.Height <= 0)
return;
double width = Bounds.Width;
double height = Bounds.Height;
int count = Items.Count;
double barWidth = (width - (count - 1) * 2) / count;
double maxValue = 100.0;
var backgroundBrush = new SolidColorBrush(Color.Parse("#1E1E2E"));
context.DrawRectangle(backgroundBrush, null, new Rect(0, 0, width, height));
for (int i = 0; i < count; i++)
{
var item = Items[i];
double barHeight = (item.Value / maxValue) * height * 0.9;
double x = i * (barWidth + 2);
double y = height - barHeight;
var brush = GetBrushForState(item.State);
context.DrawRectangle(brush, null, new Rect(x, y, barWidth, barHeight));
}
}
private IBrush GetBrushForState(Models.ArrayItemState state)
{
return state switch
{
Models.ArrayItemState.Comparing => new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute),
EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute),
GradientStops =
[
new GradientStop(Color.Parse("#FF6B6B"), 0),
new GradientStop(Color.Parse("#FF8E8E"), 1)
]
},
Models.ArrayItemState.Swapping => new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute),
EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute),
GradientStops =
[
new GradientStop(Color.Parse("#FFE66D"), 0),
new GradientStop(Color.Parse("#FFB347"), 1)
]
},
Models.ArrayItemState.Sorted => new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute),
EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute),
GradientStops =
[
new GradientStop(Color.Parse("#4ADE80"), 0),
new GradientStop(Color.Parse("#22C55E"), 1)
]
},
Models.ArrayItemState.Pivot => new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute),
EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute),
GradientStops =
[
new GradientStop(Color.Parse("#C084FC"), 0),
new GradientStop(Color.Parse("#A855F7"), 1)
]
},
_ => new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute),
EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute),
GradientStops =
[
new GradientStop(Color.Parse("#6366F1"), 0),
new GradientStop(Color.Parse("#4F46E5"), 1)
]
}
};
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
namespace InternalSortMethods.Models;
public static class SortAlgorithmsProvider
{
public static IReadOnlyList<SortingAlgorithm> Algorithms { get; } =
[
new BubbleSort(),
new SelectionSort(),
new InsertionSort(),
new QuickSort(),
new MergeSort(),
new HeapSort()
];
}
public partial class ArrayItemModel : ObservableObject
{
[ObservableProperty]
private int _value;
[ObservableProperty]
private int _index;
[ObservableProperty]
private ArrayItemState _state = ArrayItemState.Normal;
}
public enum ArrayItemState
{
Normal,
Comparing,
Swapping,
Sorted,
Pivot
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace InternalSortMethods.Models;
public enum SortActionType
{
Compare,
Swap,
Set,
MarkSorted,
MarkPivot
}
public record SortStep(int Index1, int Index2, SortActionType Action, int[]? ArraySnapshot = null);
public record SortState(int[] Array, List<SortStep> Steps, int CurrentStep, bool IsComplete);
public class SortingResult
{
public required int[] SortedArray { get; init; }
public required List<SortStep> Steps { get; init; } = [];
}

View File

@@ -0,0 +1,277 @@
using System.Collections.Generic;
namespace InternalSortMethods.Models;
public abstract class SortingAlgorithm
{
public abstract string Name { get; }
public abstract string NameRu { get; }
protected List<SortStep> Steps { get; } = [];
protected int[] _array = [];
public SortingResult Sort(int[] array)
{
_array = (int[])array.Clone();
Steps.Clear();
OnSort();
return new SortingResult
{
SortedArray = _array,
Steps = [.. Steps]
};
}
protected void Compare(int i, int j)
{
Steps.Add(new SortStep(i, j, SortActionType.Compare));
}
protected void Swap(int i, int j)
{
(_array[i], _array[j]) = (_array[j], _array[i]);
Steps.Add(new SortStep(i, j, SortActionType.Swap, (int[])_array.Clone()));
}
protected void SetValue(int index, int value)
{
_array[index] = value;
Steps.Add(new SortStep(index, -1, SortActionType.Set, (int[])_array.Clone()));
}
protected void MarkSorted(int index)
{
Steps.Add(new SortStep(index, -1, SortActionType.MarkSorted));
}
protected void MarkPivot(int index)
{
Steps.Add(new SortStep(index, -1, SortActionType.MarkPivot));
}
protected abstract void OnSort();
}
public class BubbleSort : SortingAlgorithm
{
public override string Name => "Bubble Sort";
public override string NameRu => "Пузырьковая";
protected override void OnSort()
{
int n = _array.Length;
for (int i = 0; i < n - 1; i++)
{
bool swapped = false;
for (int j = 0; j < n - i - 1; j++)
{
Compare(j, j + 1);
if (_array[j] > _array[j + 1])
{
Swap(j, j + 1);
swapped = true;
}
}
MarkSorted(n - i - 1);
if (!swapped) break;
}
MarkSorted(0);
}
}
public class SelectionSort : SortingAlgorithm
{
public override string Name => "Selection Sort";
public override string NameRu => "Выбором";
protected override void OnSort()
{
int n = _array.Length;
for (int i = 0; i < n - 1; i++)
{
int minIdx = i;
for (int j = i + 1; j < n; j++)
{
Compare(j, minIdx);
if (_array[j] < _array[minIdx])
minIdx = j;
}
if (minIdx != i)
Swap(i, minIdx);
MarkSorted(i);
}
MarkSorted(n - 1);
}
}
public class InsertionSort : SortingAlgorithm
{
public override string Name => "Insertion Sort";
public override string NameRu => "Вставками";
protected override void OnSort()
{
int n = _array.Length;
for (int i = 1; i < n; i++)
{
int key = _array[i];
int j = i - 1;
Compare(j, i);
while (j >= 0 && _array[j] > key)
{
SetValue(j + 1, _array[j]);
j--;
if (j >= 0) Compare(j, i);
}
SetValue(j + 1, key);
}
MarkSorted(0);
}
}
public class QuickSort : SortingAlgorithm
{
public override string Name => "Quick Sort";
public override string NameRu => "Быстрая";
protected override void OnSort()
{
QuickSortRecursive(0, _array.Length - 1);
MarkSorted(0);
}
private void QuickSortRecursive(int low, int high)
{
if (low < high)
{
int pi = Partition(low, high);
QuickSortRecursive(low, pi - 1);
QuickSortRecursive(pi + 1, high);
}
}
private int Partition(int low, int high)
{
int pivot = _array[high];
MarkPivot(high);
int i = low - 1;
for (int j = low; j < high; j++)
{
Compare(j, high);
if (_array[j] < pivot)
{
i++;
if (i != j) Swap(i, j);
}
}
if (i + 1 != high) Swap(i + 1, high);
MarkSorted(i + 1);
return i + 1;
}
}
public class MergeSort : SortingAlgorithm
{
public override string Name => "Merge Sort";
public override string NameRu => "Слиянием";
protected override void OnSort()
{
MergeSortRecursive(0, _array.Length - 1);
MarkSorted(0);
}
private void MergeSortRecursive(int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2;
MergeSortRecursive(left, mid);
MergeSortRecursive(mid + 1, right);
Merge(left, mid, right);
}
}
private void Merge(int left, int mid, int right)
{
int n1 = mid - left + 1;
int n2 = right - mid;
int[] leftArr = new int[n1];
int[] rightArr = new int[n2];
for (int x = 0; x < n1; x++) leftArr[x] = _array[left + x];
for (int x = 0; x < n2; x++) rightArr[x] = _array[mid + 1 + x];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2)
{
Compare(left + i, mid + 1 + j);
if (leftArr[i] <= rightArr[j])
{
SetValue(k, leftArr[i]);
i++;
}
else
{
SetValue(k, rightArr[j]);
j++;
}
k++;
}
while (i < n1)
{
SetValue(k, leftArr[i]);
i++; k++;
}
while (j < n2)
{
SetValue(k, rightArr[j]);
j++; k++;
}
}
}
public class HeapSort : SortingAlgorithm
{
public override string Name => "Heap Sort";
public override string NameRu => "Кучей";
protected override void OnSort()
{
int n = _array.Length;
for (int i = n / 2 - 1; i >= 0; i--)
Heapify(n, i);
for (int i = n - 1; i > 0; i--)
{
Swap(0, i);
MarkSorted(i);
Heapify(i, 0);
}
MarkSorted(0);
}
private void Heapify(int heapSize, int root)
{
int largest = root;
int left = 2 * root + 1;
int right = 2 * root + 2;
if (left < heapSize)
{
Compare(left, largest);
if (_array[left] > _array[largest])
largest = left;
}
if (right < heapSize)
{
Compare(right, largest);
if (_array[right] > _array[largest])
largest = right;
}
if (largest != root)
{
Swap(root, largest);
Heapify(heapSize, largest);
}
}
}

View File

@@ -1,8 +1,262 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using InternalSortMethods.Models;
namespace InternalSortMethods.ViewModels;
public partial class MainViewModel : ViewModelBase
{
[ObservableProperty] private string _greeting = "Welcome to Avalonia!";
private CancellationTokenSource? _cancellationTokenSource;
private SortingResult? _currentResult;
private int _currentStepIndex;
private readonly ManualResetEventSlim _pauseEvent = new(true);
[ObservableProperty] private ObservableCollection<ArrayItemModel> _arrayItems = [];
[ObservableProperty] private ObservableCollection<SortingAlgorithm> _algorithms = [];
[ObservableProperty] private SortingAlgorithm? _selectedAlgorithm;
[ObservableProperty] private int _arraySize = 20;
[ObservableProperty] private double _animationSpeed = 500;
[ObservableProperty] private bool _isRunning;
[ObservableProperty] private bool _isPaused;
[ObservableProperty] private bool _isComplete;
[ObservableProperty] private string _statusText = "Готов";
public MainViewModel()
{
foreach (var algo in SortAlgorithmsProvider.Algorithms)
Algorithms.Add(algo);
SelectedAlgorithm = Algorithms[0];
GenerateArray();
}
partial void OnSelectedAlgorithmChanged(SortingAlgorithm? value)
{
if (value != null && !IsRunning)
ResetCommand.Execute(null);
}
partial void OnArraySizeChanged(int value)
{
if (!IsRunning)
GenerateArray();
}
[RelayCommand]
private void GenerateArray()
{
ArrayItems.Clear();
var random = new Random();
int maxVal = 100;
for (int i = 0; i < ArraySize; i++)
{
ArrayItems.Add(new ArrayItemModel
{
Value = random.Next(10, maxVal),
Index = i,
State = ArrayItemState.Normal
});
}
ResetStates();
StatusText = "Массив сгенерирован";
}
[RelayCommand(CanExecute = nameof(CanStart))]
private async Task StartAsync()
{
if (IsComplete || _currentResult == null)
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
var token = _cancellationTokenSource.Token;
IsRunning = true;
IsComplete = false;
StatusText = $"Сортировка: {SelectedAlgorithm?.NameRu}";
var arr = GetCurrentArray();
_currentResult = SelectedAlgorithm!.Sort(arr);
_currentStepIndex = 0;
_ = Task.Run(() => ProcessStepsAsync(token), token);
}
else
{
_pauseEvent.Set();
IsPaused = false;
StatusText = $"Продолжено";
}
StartCommand.NotifyCanExecuteChanged();
PauseCommand.NotifyCanExecuteChanged();
StepForwardCommand.NotifyCanExecuteChanged();
ResetCommand.NotifyCanExecuteChanged();
}
private bool CanStart() => !IsRunning || IsPaused || IsComplete;
[RelayCommand(CanExecute = nameof(CanPause))]
private void Pause()
{
_pauseEvent.Reset();
IsPaused = true;
StatusText = "Пауза";
StartCommand.NotifyCanExecuteChanged();
PauseCommand.NotifyCanExecuteChanged();
StepForwardCommand.NotifyCanExecuteChanged();
}
private bool CanPause() => IsRunning && !IsPaused;
[RelayCommand(CanExecute = nameof(CanStep))]
private async Task StepForward()
{
if (_currentResult == null || _currentStepIndex >= _currentResult.Steps.Count)
return;
_pauseEvent.Reset();
IsPaused = true;
await ApplyStepAsync(_currentResult.Steps[_currentStepIndex]);
_currentStepIndex++;
if (_currentStepIndex >= _currentResult.Steps.Count)
Complete();
StartCommand.NotifyCanExecuteChanged();
PauseCommand.NotifyCanExecuteChanged();
StepForwardCommand.NotifyCanExecuteChanged();
}
private bool CanStep() => IsRunning && _currentResult != null && _currentStepIndex < _currentResult.Steps.Count;
[RelayCommand]
private void Reset()
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
_currentResult = null;
_currentStepIndex = 0;
IsRunning = false;
IsPaused = false;
IsComplete = false;
_pauseEvent.Set();
ResetStates();
StatusText = "Сброшено";
StartCommand.NotifyCanExecuteChanged();
PauseCommand.NotifyCanExecuteChanged();
StepForwardCommand.NotifyCanExecuteChanged();
ResetCommand.NotifyCanExecuteChanged();
}
private async Task ProcessStepsAsync(CancellationToken token)
{
try
{
while (_currentStepIndex < _currentResult!.Steps.Count && !token.IsCancellationRequested)
{
_pauseEvent.Wait(token);
if (token.IsCancellationRequested) break;
await ApplyStepAsync(_currentResult.Steps[_currentStepIndex]);
_currentStepIndex++;
await Task.Delay((int)AnimationSpeed, token);
}
if (!token.IsCancellationRequested)
Complete();
}
catch (OperationCanceledException) { }
}
private async Task ApplyStepAsync(SortStep step)
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
for (int i = 0; i < ArrayItems.Count; i++)
{
if (ArrayItems[i].State != ArrayItemState.Sorted)
ArrayItems[i].State = ArrayItemState.Normal;
}
int i1 = step.Index1;
int i2 = step.Index2;
switch (step.Action)
{
case SortActionType.Compare:
if (i1 >= 0 && i1 < ArrayItems.Count)
ArrayItems[i1].State = ArrayItemState.Comparing;
if (i2 >= 0 && i2 < ArrayItems.Count)
ArrayItems[i2].State = ArrayItemState.Comparing;
break;
case SortActionType.Swap:
if (i1 >= 0 && i1 < ArrayItems.Count && i2 >= 0 && i2 < ArrayItems.Count)
{
(ArrayItems[i1].Value, ArrayItems[i2].Value) = (ArrayItems[i2].Value, ArrayItems[i1].Value);
ArrayItems[i1].State = ArrayItemState.Swapping;
ArrayItems[i2].State = ArrayItemState.Swapping;
}
break;
case SortActionType.Set:
if (i1 >= 0 && i1 < ArrayItems.Count && step.ArraySnapshot != null && i1 < step.ArraySnapshot.Length)
ArrayItems[i1].Value = step.ArraySnapshot[i1];
break;
case SortActionType.MarkSorted:
if (i1 >= 0 && i1 < ArrayItems.Count)
ArrayItems[i1].State = ArrayItemState.Sorted;
break;
case SortActionType.MarkPivot:
if (i1 >= 0 && i1 < ArrayItems.Count)
ArrayItems[i1].State = ArrayItemState.Pivot;
break;
}
});
}
private void Complete()
{
IsRunning = false;
IsComplete = true;
Dispatcher.UIThread.Post(() =>
{
foreach (var item in ArrayItems)
item.State = ArrayItemState.Sorted;
StatusText = "Сортировка завершена!";
StartCommand.NotifyCanExecuteChanged();
PauseCommand.NotifyCanExecuteChanged();
StepForwardCommand.NotifyCanExecuteChanged();
ResetCommand.NotifyCanExecuteChanged();
});
}
private int[] GetCurrentArray()
{
int[] arr = new int[ArrayItems.Count];
for (int i = 0; i < ArrayItems.Count; i++)
arr[i] = ArrayItems[i].Value;
return arr;
}
private void ResetStates()
{
foreach (var item in ArrayItems)
item.State = ArrayItemState.Normal;
}
}

View File

@@ -3,14 +3,110 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:InternalSortMethods.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:controls="using:InternalSortMethods.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="InternalSortMethods.Views.MainView"
x:DataType="vm:MainViewModel">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainViewModel />
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UserControl>
<UserControl.Resources>
<ControlTheme x:Key="GlassButton" TargetType="Button">
<Setter Property="Background" Value="#40666EF1"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="Padding" Value="16,12"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="MinHeight" Value="48"/>
<Setter Property="MinWidth" Value="80"/>
</ControlTheme>
</UserControl.Resources>
<Grid RowDefinitions="Auto,*,Auto">
<Border Grid.Row="0" Background="#20000000" CornerRadius="16" Margin="16,16,16,8" Padding="16">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,*,Auto">
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="12">
<TextBlock Text="Алгоритм сортировки" FontSize="12" Foreground="#AAFFFFFF"/>
<ComboBox ItemsSource="{Binding Algorithms}"
SelectedItem="{Binding SelectedAlgorithm}"
MinWidth="180" MinHeight="44"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameRu}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" Spacing="12" Margin="16,0">
<TextBlock Text="Размер массива" FontSize="12" Foreground="#AAFFFFFF"/>
<Grid ColumnDefinitions="*,Auto">
<Slider Grid.Column="0" Minimum="5" Maximum="50" Value="{Binding ArraySize}"
TickFrequency="5" IsSnapToTickEnabled="True" MinHeight="44"/>
<TextBlock Grid.Column="1" Text="{Binding ArraySize}" Width="30" Margin="8,0"
VerticalAlignment="Center" Foreground="White"/>
</Grid>
</StackPanel>
<Button Grid.Row="0" Grid.Column="2" Content="Новый массив"
Command="{Binding GenerateArrayCommand}"
Theme="{StaticResource GlassButton}"
VerticalAlignment="Bottom" MinWidth="120"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="3" Spacing="12" Margin="0,16,0,0">
<TextBlock Text="Скорость анимации" FontSize="12" Foreground="#AAFFFFFF"/>
<Grid ColumnDefinitions="*,Auto">
<Slider Grid.Column="0" Minimum="50" Maximum="1000" Value="{Binding AnimationSpeed}"
TickFrequency="50" IsSnapToTickEnabled="True" MinHeight="44"/>
<TextBlock Grid.Column="1" Text="{Binding AnimationSpeed, StringFormat={}{0:0}} мс" Width="50" Margin="8,0"
VerticalAlignment="Center" Foreground="White"/>
</Grid>
</StackPanel>
</Grid>
</Border>
<Border Grid.Row="1" Background="#15FFFFFF" CornerRadius="16" Margin="16,8" Padding="16">
<controls:ArrayVisualizer Items="{Binding ArrayItems}"/>
</Border>
<Border Grid.Row="2" Background="#20000000" CornerRadius="16" Margin="16,8,16,16" Padding="16">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,*,*,*,*">
<TextBlock Grid.Row="0" Grid.ColumnSpan="5" Text="{Binding StatusText}"
FontSize="16" FontWeight="SemiBold" Foreground="White"
HorizontalAlignment="Center" Margin="0,0,0,16"/>
<Button Grid.Row="1" Grid.Column="0" Content="Старт"
Command="{Binding StartCommand}"
Theme="{StaticResource GlassButton}"
HorizontalAlignment="Stretch" Margin="0,0,8,0"/>
<Button Grid.Row="1" Grid.Column="1" Content="Пауза"
Command="{Binding PauseCommand}"
Theme="{StaticResource GlassButton}"
HorizontalAlignment="Stretch" Margin="0,0,8,0"/>
<Button Grid.Row="1" Grid.Column="2" Content="Шаг"
Command="{Binding StepForwardCommand}"
Theme="{StaticResource GlassButton}"
HorizontalAlignment="Stretch" Margin="0,0,8,0"/>
<Button Grid.Row="1" Grid.Column="3" Content="Сброс"
Command="{Binding ResetCommand}"
Theme="{StaticResource GlassButton}"
HorizontalAlignment="Stretch" Margin="0,0,8,0"/>
<StackPanel Grid.Row="1" Grid.Column="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Ellipse Width="12" Height="12" Fill="#4ADE80" Margin="0,0,8,0"
IsVisible="{Binding IsComplete}"/>
<Ellipse Width="12" Height="12" Fill="#FFE66D" Margin="0,0,8,0"
IsVisible="{Binding IsPaused}"/>
<Ellipse Width="12" Height="12" Fill="#6366F1"
IsVisible="{Binding IsRunning}"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>