| 1 | using System.Windows.Input; |
| 2 | using Avalonia; |
| 3 | using Avalonia.Controls; |
| 4 | using Avalonia.Input; |
| 5 | using Avalonia.Media; |
| 6 | using CascadeIDE.ViewModels; |
| 7 | using CascadeIDE.Views.SkiaKit.Graph; |
| 8 | |
| 9 | namespace CascadeIDE.Views; |
| 10 | |
| 11 | /// <summary>Мини-карта навигации по коду: рёбра и узлы (звезда); клик по узлу открывает файл; подписи строк — в списке (режим list/both) (ADR 0039).</summary> |
| 12 | /// <remarks> |
| 13 | /// Порядок отрисовки базовой сцены (ADR 0055 §4): рёбра → узлы (фигуры и глифы) → легенда. Подсветки TraceFlow приходят в <see cref="CodeNavigationMapGraphSceneVm"/> и рисуются вместе с рёбрами/узлами по флагам highlight. |
| 14 | /// Отрисовка — <see cref="SkiaGraphSceneDrawing"/> (SkiaKit, ADR 0117). |
| 15 | /// </remarks> |
| 16 | public sealed class CodeNavigationMapMiniMapControl : Control |
| 17 | { |
| 18 | public static readonly StyledProperty<CodeNavigationMapGraphSceneVm?> SceneProperty = |
| 19 | AvaloniaProperty.Register<CodeNavigationMapMiniMapControl, CodeNavigationMapGraphSceneVm?>(nameof(Scene)); |
| 20 | |
| 21 | public static readonly StyledProperty<ICommand?> OpenFileCommandProperty = |
| 22 | AvaloniaProperty.Register<CodeNavigationMapMiniMapControl, ICommand?>(nameof(OpenFileCommand)); |
| 23 | |
| 24 | static CodeNavigationMapMiniMapControl() |
| 25 | { |
| 26 | AffectsRender<CodeNavigationMapMiniMapControl>(SceneProperty); |
| 27 | HeightProperty.OverrideDefaultValue<CodeNavigationMapMiniMapControl>(SkiaGraphViewportMetrics.DefaultHeightFile); |
| 28 | MinWidthProperty.OverrideDefaultValue<CodeNavigationMapMiniMapControl>(200); |
| 29 | } |
| 30 | |
| 31 | public CodeNavigationMapGraphSceneVm? Scene |
| 32 | { |
| 33 | get => GetValue(SceneProperty); |
| 34 | set => SetValue(SceneProperty, value); |
| 35 | } |
| 36 | |
| 37 | public ICommand? OpenFileCommand |
| 38 | { |
| 39 | get => GetValue(OpenFileCommandProperty); |
| 40 | set => SetValue(OpenFileCommandProperty, value); |
| 41 | } |
| 42 | |
| 43 | protected override void OnPointerPressed(PointerPressedEventArgs e) |
| 44 | { |
| 45 | base.OnPointerPressed(e); |
| 46 | var scene = Scene; |
| 47 | var cmd = OpenFileCommand; |
| 48 | if (scene is null || cmd is null) |
| 49 | return; |
| 50 | var layout = GraphLayoutSceneMapper.FromViewModel(scene); |
| 51 | var p = SkiaGraphSceneHitTesting.MapControlPointToLayout( |
| 52 | e.GetPosition(this), |
| 53 | Bounds.Width, |
| 54 | Bounds.Height, |
| 55 | scene.LayoutViewportWidth, |
| 56 | scene.LayoutViewportHeight); |
| 57 | var hit = SkiaGraphSceneHitTesting.FindNodeAt(layout, p); |
| 58 | if (hit is null || string.IsNullOrWhiteSpace(hit.FullPath)) |
| 59 | return; |
| 60 | |
| 61 | var vmNode = scene.Nodes.FirstOrDefault(n => string.Equals(n.Id, hit.Id, StringComparison.OrdinalIgnoreCase)); |
| 62 | if (vmNode is null) |
| 63 | return; |
| 64 | |
| 65 | var payload = new CodeNavigationMapNodeNavigatePayload( |
| 66 | vmNode.FullPath, |
| 67 | vmNode.LineStart, |
| 68 | vmNode.LineEnd, |
| 69 | vmNode.LegendLine, |
| 70 | vmNode.Kind); |
| 71 | if (!cmd.CanExecute(payload)) |
| 72 | return; |
| 73 | try |
| 74 | { |
| 75 | cmd.Execute(payload); |
| 76 | e.Handled = true; |
| 77 | } |
| 78 | catch |
| 79 | { |
| 80 | // RelayCommand/ICommand: не роняем UI при кривом пути или сбое дока |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | public override void Render(DrawingContext context) |
| 85 | { |
| 86 | base.Render(context); |
| 87 | var scene = Scene; |
| 88 | if (scene is null || scene.IsEmpty) |
| 89 | return; |
| 90 | |
| 91 | var w = Bounds.Width; |
| 92 | var h = Bounds.Height; |
| 93 | if (w <= 0 || h <= 0) |
| 94 | return; |
| 95 | |
| 96 | var layout = GraphLayoutSceneMapper.FromViewModel(scene); |
| 97 | var theme = SkiaGraphVisualTheme.ForPresentation(layout.Presentation); |
| 98 | SkiaGraphSceneDrawing.DrawScene(context, layout, theme, w, h); |
| 99 | } |
| 100 | } |
| 101 | |