Продолжение рефакторинга (вынесение WPF в отдельный проект)

This commit is contained in:
2025-10-02 17:25:48 +04:00
parent 2fa94851cb
commit f5b311e025
161 changed files with 899 additions and 559 deletions
@@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using FontAwesome.Sharp;
using Color = System.Windows.Media.Color;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Строка структуры, представляющая поле сущности.
/// </summary>
internal class StructureRow : INotifyPropertyChanged
{
/// <summary>
/// Отображаемая строка поля сущности.
/// </summary>
public StructureField Field { get; }
/// <summary>
/// Список типов лестницы.
/// </summary>
public List<LadderType> Ladder { get; set; } = [];
/// <summary>
/// Флаг наличия кнопки.
/// </summary>
public bool HasButton =>
Field.FieldType == StructureFieldType.Complex && ((StructureFieldComplex)Field).Collapsable;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public bool Visible => Field.Visible;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public Visibility ItemVisibility => Visible ? Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public Visibility ButtonVisibility => HasButton ? Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Иконка кнопки.
/// </summary>
public string ButtonIcon =>
HasButton && !((StructureFieldComplex)Field).Collapsed
? IconChar.MinusSquare.ToChar()
: IconChar.PlusSquare.ToChar();
/// <summary>
/// Глубина отображения поля.
/// </summary>
public int Depth => Field.Depth;
/// <summary>
/// Строковое представление названия поля.
/// </summary>
public string Name => Field.Name;
/// <summary>
/// Строковое представление типа поля.
/// </summary>
public string Type
{
get
{
string result = Field.GetTypeString();
if (result != "")
{
result = $@"({result})";
}
return result;
}
}
/// <summary>
/// Строковое представление значения поля.
/// </summary>
public string Value => Field.GetValueString().Replace("\r", "").Replace("\n", "");
/// <summary>
/// Цвет значения
/// </summary>
public Color? AccentColor =>
Field switch
{
StructureFieldPrimitive primitive => System.Type.GetTypeCode(primitive.Type) switch
{
TypeCode.Boolean => Colors.DarkGoldenrod,
TypeCode.Int32 => Colors.Blue,
TypeCode.Double => Colors.Aqua,
TypeCode.String => Colors.Green,
_ => null,
},
StructureFieldEnum => Colors.Red,
StructureFieldNull => Colors.Transparent,
_ => null,
};
/// <summary>
/// Команда переключения.
/// </summary>
public ICommand CollapseCommand { get; }
/// <summary>
/// Кониструктор класса <see cref="StructureRow"/>.
/// </summary>
/// <param name="field">Поле.</param>
public StructureRow(StructureField field)
{
Field = field;
CollapseCommand = new CollapseCommand(this);
Field.PropertyChanged += Field_PropertyChanged;
}
private void Field_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Field));
OnPropertyChanged(nameof(Visible));
OnPropertyChanged(nameof(ItemVisibility));
OnPropertyChanged(nameof(ButtonVisibility));
OnPropertyChanged(nameof(ButtonIcon));
}
/// <summary>
/// Событие изменения поля.
/// </summary>
public event Action<StructureRow, StructureField>? OnChange;
/// <summary>
/// Изменение поля.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// При изменении поля.
/// </summary>
/// <param name="propertyName">Название поля.</param>
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
}
/// <summary>
/// Команда для сворачивания/разворачивания поля структуры.
/// </summary>
/// <param name="structureRow">Строка структуры, содержащая поле.</param>
internal class CollapseCommand(StructureRow structureRow) : ICommand
{
/// <summary>
/// Строка структуры, содержащая поле, для которого выполняется команда.
/// </summary>
private StructureRow StructureRow { get; } = structureRow;
/// <summary>
/// Событие, вызываемое при изменении возможности выполнения команды.
/// </summary>
public event EventHandler? CanExecuteChanged;
/// <summary>
/// Проверяет, может ли команда быть выполнена с указанным параметром.
/// </summary>
/// <param name="parameter">Параметр, передаваемый команде.</param>
/// <returns><c>true</c>, если команда может быть выполнена; в противном случае <c>false</c>.</returns>
public bool CanExecute(object? parameter)
{
return true;
}
/// <summary>
/// Выполняет команду с указанным параметром.
/// </summary>
/// <param name="parameter">Параметр, передаваемый команде.</param>
public void Execute(object? parameter)
{
StructureFieldComplex field = (StructureFieldComplex)StructureRow.Field;
field.Collapsed = !field.Collapsed;
}
}
/// <summary>
/// Преобразователь, который адаптирует цвет в зависимости от типа целевого свойства.
/// </summary>
public class ColorAdapterConverter : IMultiValueConverter
{
/// <summary>
/// Смешивает два компнента цвета с заданным коэффициентом смешивания.
/// </summary>
/// <param name="a">Первый компонент цвета.</param>
/// <param name="b">Второй компонент цвета.</param>
/// <param name="rate">Коэффициент смешивания, где 0.0 означает полный компонент <paramref name="a"/>, а 1.0 означает полный компонент <paramref name="b"/>.</param>
/// <returns>Смешанный компонент цвета.</returns>
private byte Mix(byte a, byte b, double rate) =>
System.Convert.ToByte(Math.Round(a * rate + b * (1 - rate)));
/// <summary>
/// Преобразует массив значений в целевой тип, усредняя цвета.
/// </summary>
/// <param name="value">Массив значений, содержащий цвета.</param>
/// <param name="targetType">Целевой тип, в который нужно преобразовать значения.</param>
/// <param name="parameter">Параметр преобразования, не используется.</param>
/// <param name="culture">Культура, используемая для преобразования, не используется.</param>
/// <returns>Результат преобразования в целевой тип, который может быть <c>null</c>, если преобразование невозможно.</returns>
public object? Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
Color textColor = SystemColors.WindowTextColor;
double a = textColor.A;
double r = textColor.R;
double g = textColor.G;
double b = textColor.B;
int n = 1 + value.Length;
foreach (object item in value)
{
if (item is not Color color)
{
n--;
continue;
}
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
Color resultColor = Color.FromArgb(
System.Convert.ToByte(Math.Round(a / n)),
System.Convert.ToByte(Math.Round(r / n)),
System.Convert.ToByte(Math.Round(g / n)),
System.Convert.ToByte(Math.Round(b / n))
);
Dictionary<Type, Func<object>> @switch = new()
{
{ typeof(Brush), () => new SolidColorBrush(resultColor) },
};
return @switch.TryGetValue(targetType, out Func<object>? action) ? action() : null;
}
/// <summary>
/// Преобразует значение обратно в массив типов, но не реализован.
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public object[] ConvertBack(
object value,
Type[] targetType,
object parameter,
CultureInfo culture
)
{
throw new NotImplementedException();
}
}