using FontAwesome.Sharp;
using Font = System.Drawing.Font;
using FontStyle = System.Drawing.FontStyle;
using Image = System.Drawing.Image;
namespace Pf2eViewer.EntityTreeViewer;
///
/// Элемент управления для отображения строки поля сущности.
///
internal class EntityTreeViewerControlRow
{
///
/// Элемент управления, которому принадлежит эта строка.
///
public readonly EntityTreeViewerControl ParentControl;
///
/// Отображаемая строка поля сущности.
///
public readonly StructureField Item;
#region "Поля строки"
///
/// Кэш изображения строки.
///
public Image Cache
{
get
{
if (_cache == null)
{
UpdateCache();
}
return _cache!;
}
set => _cache = value;
}
private Image? _cache;
///
/// Кэш изображения строки при наведении.
///
public Image HoveredCache
{
get
{
if (_hoveredCache == null)
{
UpdateCache();
}
return _hoveredCache!;
}
set => _hoveredCache = value;
}
private Image? _hoveredCache;
///
/// Список типов лестницы.
///
public List Ladder
{
get => _ladder;
set
{
_ladder = value;
_cache = null;
}
}
private List _ladder = [];
///
/// Проверяет, находится ли курсор над строкой.
///
public bool Hovered
{
get => _hovered;
set
{
if (value != _hovered)
{
_hovered = value;
Invalidate();
}
}
}
private bool _hovered = false;
///
/// Индекс строки в представлении.
///
public int ViewIndex { get; set; } // = -1;
///
/// Кнопка раскрытия/сворачивания.
///
public IconButton? Button { get; set; } = null;
#endregion
#region "Поля для рендеринга"
///
public Font Font
{
get => ParentControl.Font;
set => ParentControl.Font = value;
}
///
public Color ForeColor
{
get => ParentControl.ForeColor;
set => ParentControl.ForeColor = value;
}
///
public Color BackColor
{
get => ParentControl.BackColor;
set => ParentControl.BackColor = value;
}
///
public Color HoverColor
{
get => ParentControl.HoverColor;
set => ParentControl.HoverColor = value;
}
///
public int ItemHeight
{
get => ParentControl.ItemHeight;
set => ParentControl.ItemHeight = value;
}
///
public int DepthSize
{
get => ParentControl.DepthSize;
set => ParentControl.DepthSize = value;
}
///
public int ButtonSize
{
get => ParentControl.ButtonSize;
set => ParentControl.ButtonSize = value;
}
///
public float TypeFontSize
{
get => ParentControl.TypeFontSize;
set => ParentControl.TypeFontSize = value;
}
#endregion
#region "Вычисляемые поля"
///
/// Ширина строки.
///
public int Width => ParentControl.ContentWidth;
///
/// Высота строки.
///
public int Height => ItemHeight;
///
/// Флаг наличия кнопки.
///
public bool HasButton => Item.FieldType == StructureFieldType.Complex;
///
/// Глубина отображения строки.
///
public int Depth => Item.Depth;
///
/// Ширина имени в строке.
///
public int NameWidth
{
get
{
Graphics g = Graphics.FromImage(Cache);
return Convert.ToInt32(g.MeasureString(Item.Name, Font).Width + DepthSize);
}
}
///
/// Строковое представление типа поля.
///
public string TypeText
{
get
{
string result = Item.GetTypeString();
if (result != "")
{
result = $@"({result})";
}
return result;
}
}
///
/// Шрифт для отображения типа поля.
///
public Font TypeFont => new(Font.FontFamily, TypeFontSize);
///
/// Строковое представление значения поля.
///
public string ValueText => Item.GetValueString();
///
/// Шрифт для отображения значения поля.
///
public Font ValueFont =>
new(
Font.FontFamily,
Font.Size,
Item.FieldType == StructureFieldType.Complex ? FontStyle.Italic : FontStyle.Regular
);
///
/// Прямоугольник, занимаемый строкой в представлении.
///
public RectangleF Target => new(0, ViewIndex * Height, Width, Height);
///
/// Прямоугольник, занимаемый строкой в родительском представлении.
///
public RectangleF RealTarget =>
new(0, ViewIndex * Height - ParentControl.VerticalScroll.Value, Width, Height);
#endregion
///
/// Список данных для рендеринга строк.
///
private readonly List _renderData = [];
#region "Основные методы"
///
/// Создание элемента управления строки поля сущности.
///
/// Родительский элемент управления.
/// Строка поля структуры объекта.
public EntityTreeViewerControlRow(EntityTreeViewerControl parent, StructureField item)
{
ParentControl = parent;
Item = item;
ApplyItem();
UpdateCache();
UpdateButton();
}
///
/// Применение свойств к элементу управления.
///
private void ApplyItem()
{
_renderData.Clear();
_renderData.Add(new(Item.Name, Font, ForeColor));
_renderData.Add(new(TypeText, TypeFont, GetValueColor()));
_renderData.Add(
new(ValueText.Replace("\r", "").Replace('\n', ' '), ValueFont, GetValueColor())
);
if (HasButton)
{
Button = new()
{
Width = ButtonSize,
Height = ButtonSize,
IconSize = ButtonSize,
IconChar = IconChar.MinusSquare,
ImageAlign = ContentAlignment.MiddleCenter,
TextAlign = ContentAlignment.MiddleCenter,
Visible = false,
};
Button.Click += Button_Click;
ParentControl.ContentPanel.Controls.Add(Button);
}
}
///
/// Обновление кэша изображения строки.
///
public void UpdateCache()
{
Cache = RenderCache(BackColor);
HoveredCache = RenderCache(HoverColor);
}
///
/// Создание кэша изображения строки.
///
/// Цвет заднего фона.
/// Кеш изображения строки.
private Image RenderCache(Color backColor)
{
Image cache = new Bitmap(Width, Height);
Graphics g = Graphics.FromImage(cache);
g.FillRectangle(new SolidBrush(backColor), 0, 0, Width, Height);
RenderLadder(g);
RenderContent(g);
g.Save();
return cache;
}
///
/// Запрос на перерисовку строки.
///
/// Графический объект.
public void Invalidate(Graphics? g = null)
{
g ??= Graphics.FromImage(ParentControl.ContentImage);
g.DrawImage(Hovered ? HoveredCache : Cache, Target);
UpdateButton();
}
#endregion
#region "Кнопка"
///
/// Обновление кнопки раскрытия/сворачивания.
///
public void UpdateButton()
{
if (Button is null)
{
return;
}
int cx = Convert.ToInt32((0.5 + Depth) * DepthSize - 0.5 * ButtonSize);
int cy = Convert.ToInt32((0.5 + ViewIndex) * ItemHeight - 0.5 * ButtonSize);
Button.Left = cx;
Button.Top = cy;
Button!.IconChar = ((StructureFieldComplex)Item).Collapsed
? IconChar.PlusSquare
: IconChar.MinusSquare;
Button.Visible = Item.Visible;
}
///
/// Событие нажатия кнопки раскрытия/сворачивания.
///
/// Источник события.
/// Событие нажатие кнопки
private void Button_Click(object? sender, EventArgs e)
{
if (Item is StructureFieldComplex complex)
{
complex.Collapsed = !complex.Collapsed;
ParentControl.UpdateView();
}
}
#endregion
#region "Отрисовка"
///
/// Отрисовка лестницы.
///
/// Графический объект.
private void RenderLadder(Graphics g)
{
int n = Depth;
int h = ItemHeight;
int w = DepthSize;
if (_ladder.Count < Depth)
{
return;
}
Pen pen = new(Color.FromArgb(127, ForeColor.R, ForeColor.G, ForeColor.B), 1);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
float cy = 0.5f * h;
for (int i = 0; i < n; i++)
{
float cx = (i + 1.5f) * w;
switch (_ladder[i])
{
case LadderType.Last:
g.DrawLine(pen, cx, 0, cx, cy);
break;
case LadderType.Through:
g.DrawLine(pen, cx, 0, cx, h);
break;
case LadderType.Empty:
default:
break;
}
if (i == n - 1)
{
g.DrawLine(pen, cx, cy, (i + 2) * w, cy);
}
}
}
///
/// Отрисовка содержимого строки.
///
/// Графический объект.
private void RenderContent(Graphics g)
{
float[] xs =
[
DepthSize * (Item.Depth + 1),
ParentControl.NameWidth,
ParentControl.NameWidth + g.MeasureString(TypeText, TypeFont).Width + 3,
Width,
];
Pen p = new(Color.Aqua, 1);
for (int i = 0; i < 3; i++)
{
StringRenderData item = _renderData[i];
item.Left = xs[i];
item.Width = xs[i + 1] - xs[i];
item.Height = ItemHeight;
// g.DrawRectangle(p, item.Rect);
item.Draw(g);
}
}
#endregion
#region Функции-помощники в отрисовке
///
/// Смешивание цвета с цветом текста.
///
/// Цвет оттенка.
/// Степень смешения.
/// Смешенный цвет.
private Color BlendColor(Color color, double rate = 0.5)
{
return BlendColor(ForeColor, color, rate);
}
///
/// Смешивание цветов.
///
/// Первый смешиваемый цвет.
/// Второй смешиваемый цвет.
/// Степень смешения.
/// Смешенный цвет.
private Color BlendColor(Color color1, Color color2, double rate)
{
return Color.FromArgb(
Mix(color1.A, color2.A),
Mix(color1.R, color2.R),
Mix(color1.G, color2.G),
Mix(color1.B, color2.B)
);
int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate)));
}
///
/// Получение цвета текста по типу значения поля.
///
/// Цвет текста.
public Color GetValueColor()
{
return Item switch
{
StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch
{
TypeCode.Boolean => BlendColor(Color.DarkGoldenrod),
TypeCode.Int32 => BlendColor(Color.Blue),
TypeCode.Double => BlendColor(Color.Aqua),
TypeCode.String => BlendColor(Color.Green),
_ => ForeColor,
},
StructureFieldEnum => BlendColor(Color.Red),
StructureFieldNull => BlendColor(BackColor),
_ => ForeColor,
};
}
#endregion
///
/// Класс для хранения данных рендеринга строки.
///
/// Текст отображаемой строки.
/// Шрифт отображаемой строки.
/// Цвет отображаемой строки.
private class StringRenderData(string text, Font font, Color color)
{
///
/// Текст отображаемой строки.
///
public string Text { get; } = text;
///
/// Шрифт отображаемой строки.
///
public Font Font { get; } = font;
///
/// Цвет отображаемой строки.
///
public Color Color { get; } = color;
///
/// Левый край отображаемой строки.
///
public float Left { get; set; }
///
/// Ширина отображаемой строки.
///
public float Width { get; set; }
///
/// Высота отображаемой строки.
///
public float Height { get; set; }
///
/// Прямоугольник, занимаемый строкой.
///
public RectangleF Rect => new(Left, 0, Width, Height);
///
/// Уместить строку в занимаемый прямоугольник.
///
/// Элемент графики.
/// Занимаемый прямоугольник
/// Строка, на которую будет заканчиваться значнеие при переполнении.
///
private string Fit(Graphics g, RectangleF rect, string filler = "…")
{
if (Text.Length == 0 || g.MeasureString(Text, Font).Width <= rect.Width)
{
return Text;
}
string txt = Text.Length > 100 ? Text[..100] : Text;
int i = txt.Length;
while (g.MeasureString(txt + filler, Font).Width > rect.Width)
{
txt = txt[..--i];
if (i == 0)
break;
}
return txt + filler;
}
///
/// Отрисовка строки.
///
/// Элемент графики.
public void Draw(Graphics g)
{
g.DrawString(
Fit(g, Rect),
Font,
new SolidBrush(Color),
Rect,
new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center,
FormatFlags = StringFormatFlags.NoWrap,
}
);
}
}
}