Forge
csharp4405de34
1#nullable enable
2
3using Avalonia;
4using Avalonia.Controls;
5using Avalonia.Controls.Primitives;
6using Avalonia.Threading;
7
8namespace CascadeIDE.Services.MarkdownPreview;
9
10/// <summary>Fragment ids и line hints для scroll в preview.</summary>
11public sealed class MarkdownPreviewAnchorRegistry
12{
13 private readonly Dictionary<string, Control> _fragments = new(StringComparer.OrdinalIgnoreCase);
14 private readonly List<(int Line, Control Control)> _lineAnchors = new();
15
16 public ScrollViewer? ScrollHost { get; set; }
17
18 public void RegisterFragment(string id, Control control)
19 {
20 if (string.IsNullOrWhiteSpace(id))
21 return;
22
23 _fragments[id.Trim()] = control;
24 }
25
26 public void RegisterLine(int line, Control control)
27 {
28 if (line < 1)
29 return;
30
31 _lineAnchors.Add((line, control));
32 }
33
34 public void ScrollToFragment(string fragmentId)
35 {
36 if (string.IsNullOrWhiteSpace(fragmentId))
37 return;
38
39 if (!_fragments.TryGetValue(fragmentId.Trim(), out var control))
40 return;
41
42 ScrollToControl(control);
43 }
44
45 public void ScrollToLine(int line)
46 {
47 if (line < 1 || _lineAnchors.Count == 0)
48 return;
49
50 Control? best = null;
51 var bestDelta = int.MaxValue;
52 foreach (var (anchorLine, control) in _lineAnchors)
53 {
54 var delta = Math.Abs(anchorLine - line);
55 if (delta >= bestDelta)
56 continue;
57
58 bestDelta = delta;
59 best = control;
60 if (delta == 0)
61 break;
62 }
63
64 if (best is not null)
65 ScrollToControl(best);
66 }
67
68 private void ScrollToControl(Control target)
69 {
70 if (ScrollHost?.Content is not Control content)
71 return;
72
73 Dispatcher.UIThread.Post(() =>
74 {
75 var point = target.TranslatePoint(new Point(0, 0), content);
76 if (point is null)
77 return;
78
79 ScrollHost.Offset = new Vector(
80 ScrollHost.Offset.X,
81 Math.Max(0, point.Value.Y - 16));
82 }, DispatcherPriority.Loaded);
83 }
84}
85
View only · write via MCP/CIDE