Forge
csharpdeeb25a2
1#nullable enable
2
3using Avalonia;
4using CascadeIDE.Models.Intercom;
5using SkiaSharp;
6
7namespace CascadeIDE.Views.Chat.Skia;
8
9/// <summary>
10/// Единый реестр pointer hit для Skia-ленты чата: регистрация в content-space, поиск в control-space.
11/// Приоритет при перекрытии: <see cref="SkiaChatHit.RevealAttachment"/> → <see cref="SkiaChatPointerAction"/> (chrome) → z-order.
12/// </summary>
13internal sealed class SkiaChatHitRegistry
14{
15 private readonly List<(Rect Bounds, SkiaChatHit Hit)> _entries = [];
16
17 public int Count => _entries.Count;
18
19 public void Clear() => _entries.Clear();
20
21 /// <summary>Координаты как при Draw (до canvas.Restore): Y сдвигается на <paramref name="scrollOffset"/>.</summary>
22 public void RegisterContentRect(SKRect contentRect, float scrollOffset, SkiaChatHit hit) =>
23 RegisterControlRect(
24 new Rect(
25 contentRect.Left,
26 contentRect.Top - scrollOffset,
27 contentRect.Width,
28 contentRect.Height),
29 hit);
30
31 public void RegisterControlRect(Rect controlRect, SkiaChatHit hit) =>
32 _entries.Add((controlRect, hit));
33
34 public int FindIndex(Point controlPoint) =>
35 FindIndex(_entries, controlPoint);
36
37 /// <summary>Общая политика hit-test (unit-тесты и реестр).</summary>
38 public static int FindIndex(IReadOnlyList<(Rect Bounds, SkiaChatHit Hit)> entries, Point controlPoint)
39 {
40 for (var i = entries.Count - 1; i >= 0; i--)
41 {
42 if (entries[i].Bounds.Contains(controlPoint)
43 && entries[i].Hit.RevealAttachment is not null)
44 {
45 return i;
46 }
47 }
48
49 for (var i = entries.Count - 1; i >= 0; i--)
50 {
51 if (entries[i].Bounds.Contains(controlPoint)
52 && entries[i].Hit.PointerAction != SkiaChatPointerAction.None)
53 {
54 return i;
55 }
56 }
57
58 for (var i = entries.Count - 1; i >= 0; i--)
59 {
60 if (entries[i].Bounds.Contains(controlPoint))
61 return i;
62 }
63
64 return -1;
65 }
66
67 public bool ContainsPointerAction(Point controlPoint, SkiaChatPointerAction action)
68 {
69 for (var i = _entries.Count - 1; i >= 0; i--)
70 {
71 if (_entries[i].Bounds.Contains(controlPoint)
72 && _entries[i].Hit.PointerAction == action)
73 {
74 return true;
75 }
76 }
77
78 return false;
79 }
80
81 public bool TryGetHit(int index, out SkiaChatHit hit)
82 {
83 if ((uint)index >= (uint)_entries.Count)
84 {
85 hit = default;
86 return false;
87 }
88
89 hit = _entries[index].Hit;
90 return true;
91 }
92
93 public static bool WantsHandCursor(in SkiaChatHit hit) =>
94 hit.RevealAttachment is not null
95 || hit.PointerAction is SkiaChatPointerAction.ComposerSend
96 or SkiaChatPointerAction.OverviewToggle
97 or SkiaChatPointerAction.TopicTabSelect
98 or SkiaChatPointerAction.TopicTabCreate
99 or SkiaChatPointerAction.TopicTabOverflow
100 or SkiaChatPointerAction.TopicNavigatorToggle
101 or SkiaChatPointerAction.TopicNavigatorSearchFocus;
102
103 public static bool IsChromeAction(in SkiaChatHit hit) =>
104 hit.PointerAction != SkiaChatPointerAction.None;
105
106 public static bool IsComposerAction(in SkiaChatHit hit) =>
107 hit.PointerAction is SkiaChatPointerAction.ComposerSend
108 or SkiaChatPointerAction.ComposerFocus
109 or SkiaChatPointerAction.SlashPopup;
110}
111
View only · write via MCP/CIDE