Forge
csharp4405de34
1#nullable enable
2
3using Avalonia;
4using Avalonia.Input;
5using CascadeIDE.Features.Chat;
6using CascadeIDE.Views.Chat;
7using CascadeIDE.Views.Chat.Skia;
8using CascadeIDE.Views.SkiaKit;
9using SkiaSharp;
10
11namespace CascadeIDE.Views;
12
13public partial class SkiaChatSurfaceControl
14{
15 private bool TryDispatchPointerPress(Point point, PointerPressedEventArgs e)
16 {
17 var index = _chatHits.FindIndex(point);
18 if (index < 0 || !_chatHits.TryGetHit(index, out var hit))
19 return false;
20
21 if (SkiaChatHitRegistry.IsChromeAction(hit))
22 return dispatchChromePointerPress(hit, e);
23
24 return dispatchFeedPointerPress(hit, point, e);
25 }
26
27 private bool dispatchChromePointerPress(in SkiaChatHit hit, PointerPressedEventArgs e)
28 {
29 switch (hit.PointerAction)
30 {
31 case SkiaChatPointerAction.OverviewToggle:
32 OverviewMode = !OverviewMode;
33 return true;
34 case SkiaChatPointerAction.ComposerSend:
35 if (!ShowIntercomComposer)
36 return false;
37 Focus();
38 if (IsComposerEnabled)
39 SendRequested?.Invoke(this, EventArgs.Empty);
40 return true;
41 case SkiaChatPointerAction.SlashPopup when ShowIntercomComposer && _slashPopupBounds.Width > 0:
42 {
43 var popupPoint = e.GetPosition(this);
44 var row = SkiaPopupList.HitTestRow(
45 _slashPopupBounds,
46 (float)popupPoint.X,
47 (float)popupPoint.Y,
48 _slashRows.Count,
49 _slashPopupScrollOffset,
50 ShowSlashHierarchyHeader);
51 if (row < 0)
52 return false;
53 SelectedSlashSuggestionIndex = row;
54 ComposerKeyDown?.Invoke(this, new IntercomComposerKeyEventArgs(IntercomComposerKeyKind.CommitSlashSuggestion));
55 return true;
56 }
57 case SkiaChatPointerAction.CommandLineFocus:
58 if (!ShowIntercomComposer || !ShowCockpitCommandLine)
59 return false;
60 ClearNavigatorSearchFocus();
61 _commandLineFocused = true;
62 Focus();
63 var cclPoint = e.GetPosition(this);
64 TryPlaceCommandLineCaretAtPoint(
65 (float)cclPoint.X,
66 (float)cclPoint.Y,
67 e.KeyModifiers.HasFlag(KeyModifiers.Shift));
68 return true;
69 case SkiaChatPointerAction.ComposerFocus:
70 if (!ShowIntercomComposer)
71 return false;
72 ClearNavigatorSearchFocus();
73 _commandLineFocused = false;
74 Focus();
75 var composerPoint = e.GetPosition(this);
76 TryPlaceComposerCaretAtPoint(
77 (float)composerPoint.X,
78 (float)composerPoint.Y,
79 e.KeyModifiers.HasFlag(KeyModifiers.Shift));
80 return true;
81 case SkiaChatPointerAction.TopicNavigatorSearchFocus:
82 FocusNavigatorSearch();
83 var searchPoint = e.GetPosition(this);
84 TryPlaceNavigatorSearchCaretAtPoint((float)searchPoint.X, (float)searchPoint.Y);
85 return true;
86 case SkiaChatPointerAction.TopicTabSelect when hit.SelectThreadId is { } tabThreadId:
87 if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
88 {
89 TopicRenameRequested?.Invoke(this, new TopicRenameRequestEventArgs(tabThreadId, showContextMenu: true));
90 return true;
91 }
92
93 if (e.ClickCount >= 2)
94 {
95 TopicRenameRequested?.Invoke(this, new TopicRenameRequestEventArgs(tabThreadId, showContextMenu: false));
96 return true;
97 }
98
99 DetailThreadId = tabThreadId;
100 OverviewMode = false;
101 return true;
102 case SkiaChatPointerAction.TopicTabCreate:
103 TopicCreateRequested?.Invoke(this, EventArgs.Empty);
104 return true;
105 case SkiaChatPointerAction.TopicTabOverflow:
106 OverviewMode = true;
107 return true;
108 case SkiaChatPointerAction.TopicNavigatorToggle:
109 TopicNavigatorToggleRequested?.Invoke(this, EventArgs.Empty);
110 return true;
111 default:
112 return false;
113 }
114 }
115
116 private bool dispatchFeedPointerPress(in SkiaChatHit hit, Point point, PointerPressedEventArgs e)
117 {
118 if (hit.RevealAttachment is { } attachAnchor)
119 {
120 var select = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
121 AttachmentRevealRequested?.Invoke(
122 this,
123 new IntercomAttachmentRevealEventArgs(attachAnchor, select, hit.MessageIndex));
124 return true;
125 }
126
127 if (hit.ResetDetailMode)
128 {
129 OverviewMode = true;
130 return true;
131 }
132
133 if (hit.SelectThreadId is { } threadId)
134 {
135 if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
136 {
137 TopicRenameRequested?.Invoke(this, new TopicRenameRequestEventArgs(threadId, showContextMenu: true));
138 return true;
139 }
140
141 if (e.ClickCount >= 2)
142 {
143 TopicRenameRequested?.Invoke(this, new TopicRenameRequestEventArgs(threadId, showContextMenu: false));
144 return true;
145 }
146
147 DetailThreadId = threadId;
148 OverviewMode = false;
149 return true;
150 }
151
152 if (hit.MessageIndex is not { } messageIndex)
153 return false;
154
155 if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
156 {
157 MessageSelectContextRequested?.Invoke(this, messageIndex);
158 return true;
159 }
160
161 if (hit.ToggleThinking && e.ClickCount >= 2)
162 ThinkingToggleRequested?.Invoke(this, messageIndex);
163
164 return true;
165 }
166
167 private bool TryDispatchPointerWheel(Point point, PointerWheelEventArgs e)
168 {
169 if (_chatHits.ContainsPointerAction(point, SkiaChatPointerAction.SlashPopup)
170 && _slashRows.Count > 0)
171 {
172 var deltaRows = e.Delta.Y > 0 ? -1 : e.Delta.Y < 0 ? 1 : 0;
173 if (deltaRows == 0)
174 return false;
175
176 _slashPopupScrollOffset = SkiaPopupList.ClampScrollOffset(
177 _slashPopupScrollOffset + deltaRows,
178 _slashRows.Count);
179 return true;
180 }
181
182 if (_chatHits.ContainsPointerAction(point, SkiaChatPointerAction.CommandLineFocus)
183 && ShowCockpitCommandLine
184 && _commandLineFocused)
185 {
186 if (TryScrollCommandLine((float)(e.Delta.Y * WheelPixelsPerDelta)))
187 return true;
188 return true;
189 }
190
191 if (_chatHits.ContainsPointerAction(point, SkiaChatPointerAction.ComposerFocus))
192 {
193 if (TryScrollComposer((float)(e.Delta.Y * WheelPixelsPerDelta)))
194 return true;
195 return true;
196 }
197
198 return false;
199 }
200
201 private void registerChromePointerHits(SKRect overviewButtonBounds, SKRect navigatorToggleBounds)
202 {
203 if (navigatorToggleBounds.Width > 0)
204 {
205 _chatHits.RegisterControlRect(
206 SkiaChatHitGeometry.ToControlRect(navigatorToggleBounds),
207 new SkiaChatHit(null, null, ResetDetailMode: false, PointerAction: SkiaChatPointerAction.TopicNavigatorToggle));
208 }
209
210 if (overviewButtonBounds.Width > 0)
211 {
212 _chatHits.RegisterControlRect(
213 SkiaChatHitGeometry.ToControlRect(overviewButtonBounds),
214 new SkiaChatHit(null, null, ResetDetailMode: false, PointerAction: SkiaChatPointerAction.OverviewToggle));
215 }
216 }
217
218 private void registerTopicNavigatorPointerHits(SkiaIntercomTopicNavigator.LayoutResult layout, float panelLeft)
219 {
220 if (layout.SearchBounds.Width > 0)
221 {
222 _navigatorSearchBounds = layout.SearchBounds;
223 var searchBounds = layout.SearchBounds;
224 if (panelLeft != 0f)
225 searchBounds.Offset(panelLeft, 0f);
226
227 _chatHits.RegisterControlRect(
228 SkiaChatHitGeometry.ToControlRect(searchBounds),
229 new SkiaChatHit(null, null, ResetDetailMode: false, PointerAction: SkiaChatPointerAction.TopicNavigatorSearchFocus));
230 }
231
232 // RowHits.Bounds уже в координатах контрола (MapRowBoundsToPanel в Draw).
233 foreach (var row in layout.RowHits)
234 {
235 var bounds = row.Bounds;
236 if (panelLeft != 0f)
237 bounds.Offset(panelLeft, 0f);
238
239 _chatHits.RegisterControlRect(
240 SkiaChatHitGeometry.ToControlRect(bounds),
241 new SkiaChatHit(null, row.ThreadId, ResetDetailMode: false));
242 }
243 }
244
245 private void registerNavigationPointerHits(SkiaIntercomNavigationChrome.LayoutResult layout)
246 {
247 foreach (var tab in layout.TabHits)
248 {
249 _chatHits.RegisterControlRect(
250 SkiaChatHitGeometry.ToControlRect(tab.Bounds),
251 new SkiaChatHit(
252 null,
253 tab.ThreadId,
254 ResetDetailMode: false,
255 PointerAction: SkiaChatPointerAction.TopicTabSelect));
256 }
257
258 if (layout.CreateButtonBounds.Width > 0)
259 {
260 _chatHits.RegisterControlRect(
261 SkiaChatHitGeometry.ToControlRect(layout.CreateButtonBounds),
262 new SkiaChatHit(null, null, ResetDetailMode: false, PointerAction: SkiaChatPointerAction.TopicTabCreate));
263 }
264
265 if (layout.OverflowBounds is { } overflow && layout.OverflowHiddenCount > 0)
266 {
267 _chatHits.RegisterControlRect(
268 SkiaChatHitGeometry.ToControlRect(overflow),
269 new SkiaChatHit(null, null, ResetDetailMode: false, PointerAction: SkiaChatPointerAction.TopicTabOverflow));
270 }
271 }
272}
273
View only · write via MCP/CIDE