public void DrawNode(SKCanvas ctx, ComlinkNode node, bool selected) { using var headerTextPaint = _paint.Clone().WithColor(0xFF_FFFFFF).WithTypeface(_headerTypeface); using var textPaint = _paint.Clone().WithColor(0xFF_FFFFFF).WithTypeface(_nodeTypeface); var headerLineHeight = (int)(headerTextPaint.FontMetrics.Descent - headerTextPaint.FontMetrics.Ascent + headerTextPaint.FontMetrics.Leading); var headerBaselineOffset = (int)-headerTextPaint.FontMetrics.Ascent; var lineHeight = (int)(textPaint.FontMetrics.Descent - textPaint.FontMetrics.Ascent + textPaint.FontMetrics.Leading); var width = GetNodeWidth(node, headerTextPaint, textPaint); var numPins = Math.Max(node.InputPins.Count, node.OutputPins.Count); var height = lineHeight * (numPins - 1) - textPaint.FontMetrics.Ascent + textPaint.FontMetrics.Descent + 6; var x = node.X; var y = node.Y; var headerX = x - NodeBorderSize; var headerY = y - headerLineHeight; var headerWidth = width + 2 * NodeBorderSize; var headerHeight = height + headerLineHeight + NodeBorderSize; if (selected) { var selectionStrokeSize = NodeBorderSize / 2f; using var paint = _paint.Clone(); using var path = SKPathEffect.CreateDash(SelectionStrokeInterval, (float)((DateTime.Now - DateTime.Today).TotalSeconds * 10 % 20)); paint.IsStroke = true; paint.Color = new SKColor(SelectionBorderColor); paint.StrokeWidth = selectionStrokeSize; paint.PathEffect = path; ctx.DrawRoundRect(headerX - selectionStrokeSize, headerY - selectionStrokeSize, headerWidth + 2 * selectionStrokeSize, headerHeight + 2 * selectionStrokeSize, NodeCornerRadius + selectionStrokeSize, NodeCornerRadius + selectionStrokeSize, paint); } // header ctx.DrawRoundRect(headerX, headerY, headerWidth, headerHeight, NodeCornerRadius, NodeCornerRadius, _paint.WithColor(node.Color)); // title ctx.DrawCircle(x + (headerBaselineOffset - NodeBorderSize + 1) / 2f, y - headerLineHeight / 2f, headerLineHeight / 4f, headerTextPaint); ctx.DrawText(node.Name, x + headerBaselineOffset, y - headerLineHeight + headerBaselineOffset, headerTextPaint); // body ctx.DrawRoundRect(x, y, width, height, NodeCornerRadius - NodeBorderSize + 1, NodeCornerRadius - NodeBorderSize + 1, _paint.WithColor(BodyColor)); y -= (int)(textPaint.FontMetrics.Ascent - 3); var triangleRadius = lineHeight / 10f; var circleRadius = lineHeight / 3f; var pinOffset = lineHeight / 4f; var pY = y; for (var i = 0; i < node.InputPins.Count; i++, pY += lineHeight) { var pin = node.InputPins[i]; if (pin is FlowInputPin) { ctx.DrawRoundedTriangle(x - pinOffset / 2, pY - pinOffset, pinOffset, triangleRadius + NodeBorderSize, _paint.WithColor(BodyColor)); ctx.DrawRoundedTriangle(x - pinOffset / 2, pY - pinOffset, pinOffset, triangleRadius, _paint.WithColor(pin.Color)); } else { // input dot ctx.DrawCircle(x, pY - pinOffset, circleRadius, _paint.WithColor(BodyColor)); ctx.DrawCircle(x, pY - pinOffset, circleRadius - NodeBorderSize, _paint.WithColor(pin.Color)); } ctx.DrawText(pin.Name, x + lineHeight / 2f, pY, textPaint); } pY = y; for (var i = 0; i < node.OutputPins.Count; i++, pY += lineHeight) { var pin = node.OutputPins[i]; if (pin is FlowOutputPin) { ctx.DrawRoundedTriangle(x + width - pinOffset / 2, pY - pinOffset, pinOffset, triangleRadius + NodeBorderSize, _paint.WithColor(BodyColor)); ctx.DrawRoundedTriangle(x + width - pinOffset / 2, pY - pinOffset, pinOffset, triangleRadius, _paint.WithColor(pin.Color)); } else { // output dot ctx.DrawCircle(x + width, pY - pinOffset, circleRadius, _paint.WithColor(BodyColor)); ctx.DrawCircle(x + width, pY - pinOffset, circleRadius - 3, _paint.WithColor(pin.Color)); } var textWidth = textPaint.MeasureText(pin.Name); ctx.DrawText(pin.Name, x + width - textWidth - lineHeight / 2f, pY, textPaint); } }