Forge
csharp4405de34
1using System.Globalization;
2using Avalonia;
3using Avalonia.Media;
4using CascadeIDE.Cockpit.PrimitivesKit;
5using CascadeIDE.Features.WorkspaceNavigation.Application;
6
7namespace CascadeIDE.Services;
8
9/// <summary>
10/// Отрисовка узлов control-flow в редакторе — те же заливки/обводки, что <see cref="Views.SkiaKit.Graph.SkiaGraphSceneDrawing"/> (палитра CFG).
11/// </summary>
12public static class ControlFlowEditorNodePainter
13{
14 private const string ExitStepKind = "exit_step";
15 private const string ConditionStepKind = "condition_step";
16 private const string HandlerStepKind = "handler_step";
17
18 private static readonly IBrush AnchorFill = Brush(CockpitPrimitivesPalette.CodeNavigationMap.AnchorFill);
19 private static readonly IBrush ConditionFill = Brush(CockpitPrimitivesPalette.CodeNavigationMap.ConditionFill);
20 private static readonly IBrush ExitFill = Brush(CockpitPrimitivesPalette.CodeNavigationMap.ExitFill);
21 private static readonly IBrush CallFill = Brush(CockpitPrimitivesPalette.CodeNavigationMap.CallFill);
22 private static readonly IBrush HandlerFill = Brush(CockpitPrimitivesPalette.CodeNavigationMap.HandlerFill);
23 private static readonly Pen NodeStroke = new(new SolidColorBrush(CockpitPrimitivesPalette.CodeNavigationMap.NodeStroke), 1);
24 private static readonly IBrush GlyphOnFill = Brushes.White;
25 private static readonly IBrush GlyphOnExit = new SolidColorBrush(Color.FromRgb(220, 228, 240));
26
27 public static IBrush ResolveFillBrush(string nodeKind, ControlFlowNodeVisualKind visualKind)
28 {
29 if (visualKind == ControlFlowNodeVisualKind.Anchor)
30 return AnchorFill;
31 if (visualKind == ControlFlowNodeVisualKind.Diamond
32 || IsConditionKind(nodeKind))
33 return ConditionFill;
34 if (visualKind == ControlFlowNodeVisualKind.Exit || IsExitKind(nodeKind))
35 return ExitFill;
36 if (IsHandlerKind(nodeKind))
37 return HandlerFill;
38 return CallFill;
39 }
40
41 public static IBrush ResolveGlyphBrush(ControlFlowNodeVisualKind visualKind) =>
42 visualKind == ControlFlowNodeVisualKind.Exit ? GlyphOnExit : GlyphOnFill;
43
44 public static Pen NodeStrokePen => NodeStroke;
45
46 public static IBrush NodeStrokeBrush => NodeStroke.Brush ?? Brushes.Black;
47
48 public static void DrawNode(
49 DrawingContext ctx,
50 ControlFlowLineVisual visual,
51 double centerX,
52 double centerY,
53 double radius,
54 Typeface typeface,
55 double glyphFontSize)
56 {
57 var fill = ResolveFillBrush(visual.NodeKind, visual.VisualKind);
58 switch (visual.VisualKind)
59 {
60 case ControlFlowNodeVisualKind.Anchor:
61 ctx.DrawEllipse(fill, NodeStroke, new Rect(centerX - radius, centerY - radius, radius * 2, radius * 2));
62 break;
63 case ControlFlowNodeVisualKind.Diamond:
64 DrawDiamond(ctx, centerX, centerY, radius, fill, NodeStroke);
65 break;
66 case ControlFlowNodeVisualKind.Exit:
67 ctx.DrawEllipse(fill, NodeStroke, new Rect(centerX - radius, centerY - radius, radius * 2, radius * 2));
68 if (visual.ShowExitArrow)
69 DrawNeArrow(ctx, centerX, centerY, radius * 0.85);
70 DrawGlyphIfAny(ctx, visual, centerX, centerY, typeface, glyphFontSize);
71 return;
72 case ControlFlowNodeVisualKind.Circle:
73 ctx.DrawEllipse(fill, NodeStroke, new Rect(centerX - radius, centerY - radius, radius * 2, radius * 2));
74 break;
75 }
76
77 DrawGlyphIfAny(ctx, visual, centerX, centerY, typeface, glyphFontSize);
78 }
79
80 private static void DrawGlyphIfAny(
81 DrawingContext ctx,
82 ControlFlowLineVisual visual,
83 double cx,
84 double cy,
85 Typeface typeface,
86 double fontSize)
87 {
88 if (string.IsNullOrEmpty(visual.TextGlyph))
89 return;
90
91 var ft = new FormattedText(
92 visual.TextGlyph,
93 CultureInfo.InvariantCulture,
94 FlowDirection.LeftToRight,
95 typeface,
96 fontSize,
97 ResolveGlyphBrush(visual.VisualKind));
98 ctx.DrawText(ft, new Point(cx - ft.Width / 2, cy - ft.Height / 2));
99 }
100
101 private static void DrawDiamond(DrawingContext ctx, double cx, double cy, double r, IBrush fill, Pen pen)
102 {
103 var geo = new StreamGeometry();
104 using (var ig = geo.Open())
105 {
106 ig.BeginFigure(new Point(cx, cy - r), true);
107 ig.LineTo(new Point(cx + r, cy));
108 ig.LineTo(new Point(cx, cy + r));
109 ig.LineTo(new Point(cx - r, cy));
110 ig.EndFigure(true);
111 }
112
113 ctx.DrawGeometry(fill, pen, geo);
114 }
115
116 private static void DrawNeArrow(DrawingContext ctx, double cx, double cy, double len)
117 {
118 var pen = new Pen(GlyphOnExit, 1.15) { LineCap = PenLineCap.Round };
119 const double dirX = 0.70710678118654757;
120 const double dirY = -0.70710678118654757;
121 var tip = new Point(cx + dirX * (len * 0.35), cy + dirY * (len * 0.35));
122 var basePt = new Point(tip.X - dirX * len, tip.Y - dirY * len);
123 ctx.DrawLine(pen, basePt, tip);
124 var wing = len * 0.38;
125 var px = -dirY;
126 var py = dirX;
127 ctx.DrawLine(
128 pen,
129 new Point(tip.X - dirX * wing + px * wing * 0.35, tip.Y - dirY * wing + py * wing * 0.35),
130 tip);
131 ctx.DrawLine(
132 pen,
133 new Point(tip.X - dirX * wing - px * wing * 0.35, tip.Y - dirY * wing - py * wing * 0.35),
134 tip);
135 }
136
137 private static bool IsExitKind(string kind) =>
138 string.Equals(kind, ExitStepKind, StringComparison.OrdinalIgnoreCase);
139
140 private static bool IsConditionKind(string kind) =>
141 string.Equals(kind, ConditionStepKind, StringComparison.OrdinalIgnoreCase);
142
143 private static bool IsHandlerKind(string kind) =>
144 string.Equals(kind, HandlerStepKind, StringComparison.OrdinalIgnoreCase);
145
146 private static SolidColorBrush Brush(Color c) => new(c);
147}
148
View only · write via MCP/CIDE