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; // Local fields public Image Cache { get { if (_cache == null) { UpdateCache(); } return _cache!; } set => _cache = value; } private Image? _cache; 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; // Inherited fields 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; } // ValueText functions 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, Font.Size * 0.6f, FontStyle.Italic); 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); // Simple fields private readonly List _renderData = []; // Main methods 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 = new Bitmap(Width, Height); Graphics g = Graphics.FromImage(Cache); RenderLadder(g); RenderContent(g); g.Save(); } public void Invalidate(Graphics? g = null) { g ??= Graphics.FromImage(ParentControl.ContentImage); g.FillRectangle(new SolidBrush(Hovered ? HoverColor : BackColor), Target); g.DrawImage(Cache, Target); UpdateButton(); } // Button 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(); } } // Render 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); } } // Helper functions 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), _ => ForeColor, }; } 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) { 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 + "...", Font).Width > rect.Width) { txt = txt[..--i]; if (i == 0) break; } return txt + "..."; } 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, } ); } } }