Forge
csharpdeeb25a2
1#nullable enable
2using CascadeIDE.Features.Chat;
3using CascadeIDE.Models;
4using CascadeIDE.Services;
5using SkiaSharp;
6
7namespace CascadeIDE.Views.Chat.Skia;
8
9/// <summary>Skia toolbar Intercom (ADR 0123 фаза 1).</summary>
10internal static class SkiaChatChromeRenderer
11{
12 public const float ToolbarHeight = 36f;
13
14 /// <summary>Toolbar с подзаголовком (тема / линия / сообщений).</summary>
15 public const float ToolbarWithStatusHeight = 52f;
16
17 /// <summary>Заголовок режима overview (картотека) — не в скролле ленты.</summary>
18 public const float OverviewCatalogBandHeight = 44f;
19
20 public static float ResolveToolbarHeight(bool forwardHost, bool showStatusSubtitle) =>
21 forwardHost ? (showStatusSubtitle ? ToolbarWithStatusHeight : ToolbarHeight) : 0f;
22
23 public static float ResolveTopChromeHeight(bool forwardHost, bool showOverviewCatalog, bool showStatusSubtitle) =>
24 ResolveTopChromeHeight(forwardHost, showOverviewCatalog, showStatusSubtitle, overviewMode: showOverviewCatalog, topicCount: 0);
25
26 public static float ResolveTopChromeHeight(
27 bool forwardHost,
28 bool showOverviewCatalog,
29 bool showStatusSubtitle,
30 bool overviewMode,
31 int topicCount,
32 bool hasScopeStrip = false) =>
33 SkiaIntercomNavigationChrome.ResolveTopChromeHeight(
34 forwardHost,
35 showOverviewCatalog,
36 showStatusSubtitle,
37 overviewMode,
38 topicCount,
39 hasScopeStrip);
40
41 public static void Draw(
42 SKCanvas canvas,
43 float width,
44 SkiaChatTheme theme,
45 string title,
46 bool overviewMode,
47 bool isLoading,
48 string? loadingText,
49 string? statusSubtitle,
50 bool showNavigatorToggle,
51 bool navigatorVisible,
52 out SKRect overviewButtonBounds,
53 out SKRect navigatorToggleBounds,
54 IntercomFontsSettings? fonts = null)
55 {
56 navigatorToggleBounds = default;
57 fonts ??= IntercomFontDefaults.Intercom;
58 var titlePt = fonts.ResolveChromeTitlePt();
59 var subtitlePt = fonts.ResolveChromeSubtitlePt();
60 var toolbarHeight = ResolveToolbarHeight(forwardHost: true, showStatusSubtitle: !string.IsNullOrWhiteSpace(statusSubtitle));
61 canvas.DrawRect(new SKRect(0, 0, width, toolbarHeight), new SKPaint
62 {
63 Color = theme.Surface,
64 IsStroke = false,
65 });
66 canvas.DrawLine(0, toolbarHeight - 0.5f, width, toolbarHeight - 0.5f, new SKPaint
67 {
68 Color = theme.Border,
69 StrokeWidth = 1,
70 IsAntialias = true,
71 });
72
73 using var titleFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily(), SKFontStyle.Bold), titlePt);
74 using var titlePaint = new SKPaint { IsAntialias = true, Color = theme.Content };
75 canvas.DrawText(title, 12, 12 + titlePt * 0.85f, SKTextAlign.Left, titleFont, titlePaint);
76
77 if (!string.IsNullOrWhiteSpace(statusSubtitle))
78 {
79 using var statusFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily()), subtitlePt);
80 using var statusPaint = new SKPaint { IsAntialias = true, Color = theme.MutedContent };
81 canvas.DrawText(
82 Truncate(statusSubtitle, 96),
83 12,
84 12 + titlePt * 0.85f + subtitlePt + 6f,
85 SKTextAlign.Left,
86 statusFont,
87 statusPaint);
88 }
89
90 var topicsLabel = overviewMode ? "Темы ✓" : "Темы";
91 var btnPt = Math.Max(10f, subtitlePt + 1f);
92 using var btnFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily()), btnPt);
93 using var btnPaint = new SKPaint { IsAntialias = true, Color = theme.Role };
94 var topicsWidth = btnFont.MeasureText(topicsLabel) + 16;
95 var right = width - 12f;
96 overviewButtonBounds = new SKRect(right - topicsWidth, 6, right, 28);
97 right = overviewButtonBounds.Left - 8f;
98
99 if (showNavigatorToggle)
100 {
101 var navLabel = navigatorVisible ? "Nav ✓" : "☰ Nav";
102 var navWidth = btnFont.MeasureText(navLabel) + 16;
103 navigatorToggleBounds = new SKRect(right - navWidth, 6, right, 28);
104 right = navigatorToggleBounds.Left - 8f;
105 using var navBg = new SKPaint { IsAntialias = true, Color = SkiaKit.SkiaKitColor.Blend(theme.Surface, theme.Border, 0.35f) };
106 canvas.DrawRoundRect(navigatorToggleBounds, 6, 6, navBg);
107 canvas.DrawText(navLabel, navigatorToggleBounds.MidX, 20, SKTextAlign.Center, btnFont, btnPaint);
108 }
109 if (isLoading && !string.IsNullOrWhiteSpace(loadingText))
110 {
111 using var chipFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily()), subtitlePt);
112 var chipText = Truncate(loadingText, 28);
113 var chipWidth = chipFont.MeasureText(chipText) + 14;
114 var chipRect = new SKRect(right - chipWidth, 8, right, 28);
115 DrawChip(canvas, chipRect, chipText, theme, chipFont);
116 }
117
118 using var btnBg = new SKPaint { IsAntialias = true, Color = SkiaKit.SkiaKitColor.Blend(theme.Surface, theme.Border, 0.35f) };
119 canvas.DrawRoundRect(overviewButtonBounds, 6, 6, btnBg);
120 canvas.DrawText(topicsLabel, overviewButtonBounds.MidX, 20, SKTextAlign.Center, btnFont, btnPaint);
121 }
122
123 /// <summary>Полоса «Картотека тем» под toolbar — заголовок режима, не карточка в ленте.</summary>
124 public static void DrawOverviewCatalogBand(
125 SKCanvas canvas,
126 float width,
127 float top,
128 SkiaChatTheme theme,
129 int topicCount,
130 IntercomFontsSettings? fonts = null)
131 {
132 fonts ??= IntercomFontDefaults.Intercom;
133 var headingPt = fonts.ResolveChromeHeadingPt();
134 var hintPt = fonts.ResolveChromeSubtitlePt();
135 var bottom = top + OverviewCatalogBandHeight;
136 using (var fill = new SKPaint
137 {
138 Color = SkiaKit.SkiaKitColor.Blend(theme.Surface, theme.Border, 0.08f),
139 IsAntialias = true,
140 Style = SKPaintStyle.Fill,
141 })
142 canvas.DrawRect(new SKRect(0, top, width, bottom), fill);
143
144 using (var line = new SKPaint
145 {
146 Color = theme.Border,
147 StrokeWidth = 1,
148 IsAntialias = true,
149 })
150 canvas.DrawLine(0, bottom - 0.5f, width, bottom - 0.5f, line);
151
152 using var modeFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily(), SKFontStyle.Bold), headingPt);
153 using var modePaint = new SKPaint { IsAntialias = true, Color = theme.Content };
154 canvas.DrawText("Картотека тем", 12, top + headingPt * 0.85f + 6f, SKTextAlign.Left, modeFont, modePaint);
155
156 var hint = ChatThreadOverviewPresentation.FormatCatalogHint(topicCount) + " · " +
157 ChatThreadOverviewPresentation.CatalogFooter;
158 using var hintFont = new SKFont(SKTypeface.FromFamilyName(fonts.ResolveProseFamily()), hintPt);
159 using var hintPaint = new SKPaint { IsAntialias = true, Color = theme.MutedContent };
160 canvas.DrawText(hint, 12, top + headingPt * 0.85f + hintPt + 14f, SKTextAlign.Left, hintFont, hintPaint);
161 }
162
163 private static void DrawChip(SKCanvas canvas, SKRect rect, string text, SkiaChatTheme theme, SKFont font)
164 {
165 using var bg = new SKPaint { IsAntialias = true, Color = SkiaKit.SkiaKitColor.Blend(theme.BubbleAssistant, theme.Border, 0.25f) };
166 canvas.DrawRoundRect(rect, 8, 8, bg);
167 using var paint = new SKPaint { IsAntialias = true, Color = theme.MutedContent };
168 canvas.DrawText(text, rect.Left + 7, 21, SKTextAlign.Left, font, paint);
169 }
170
171 private static string Truncate(string text, int maxChars) =>
172 text.Length <= maxChars ? text : text[..maxChars] + "…";
173}
174
View only · write via MCP/CIDE