Forge
csharpdeeb25a2
1using Avalonia;
2using Avalonia.Controls;
3using Avalonia.Layout;
4using Avalonia.Input;
5using Avalonia.Interactivity;
6using Avalonia.Media;
7using Avalonia.Metadata;
8
9namespace CascadeIDE.Features.UiChrome;
10
11/// <summary>
12/// Затемнение на весь родитель и центрированная панель (как палитра команд Ctrl+Q).
13/// <see cref="PassThroughInput"/> — не перехватывать мышь (подсказки CascadeChord).
14/// </summary>
15public partial class ModalOverlay : UserControl
16{
17 public static readonly StyledProperty<object?> ChildProperty =
18 AvaloniaProperty.Register<ModalOverlay, object?>(nameof(Child));
19
20 /// <summary>Содержимое центрированной панели (первый дочерний элемент в XAML).</summary>
21 [Content]
22 public object? Child
23 {
24 get => GetValue(ChildProperty);
25 set => SetValue(ChildProperty, value);
26 }
27
28 public static readonly StyledProperty<bool> PassThroughInputProperty =
29 AvaloniaProperty.Register<ModalOverlay, bool>(nameof(PassThroughInput));
30
31 public static readonly StyledProperty<double> MaxPanelWidthProperty =
32 AvaloniaProperty.Register<ModalOverlay, double>(nameof(MaxPanelWidth), 720);
33
34 public static readonly StyledProperty<double> MaxPanelHeightProperty =
35 AvaloniaProperty.Register<ModalOverlay, double>(nameof(MaxPanelHeight), 480);
36
37 public static readonly StyledProperty<Thickness> PanelMarginProperty =
38 AvaloniaProperty.Register<ModalOverlay, Thickness>(nameof(PanelMargin), new Thickness(40));
39
40 /// <summary>Положение панели с содержимым по вертикали (палитра — по центру; лёгкий оверлей аккорда — сверху).</summary>
41 public static readonly StyledProperty<VerticalAlignment> PanelVerticalAlignmentProperty =
42 AvaloniaProperty.Register<ModalOverlay, VerticalAlignment>(nameof(PanelVerticalAlignment), VerticalAlignment.Center);
43
44 public static readonly StyledProperty<IBrush?> DimmerBrushProperty =
45 AvaloniaProperty.Register<ModalOverlay, IBrush?>(nameof(DimmerBrush), new SolidColorBrush(Color.Parse("#AA000000")));
46
47 /// <summary>Если true — весь оверлей прозрачен для hit-test (клики проходят к окну).</summary>
48 public bool PassThroughInput
49 {
50 get => GetValue(PassThroughInputProperty);
51 set => SetValue(PassThroughInputProperty, value);
52 }
53
54 public double MaxPanelWidth
55 {
56 get => GetValue(MaxPanelWidthProperty);
57 set => SetValue(MaxPanelWidthProperty, value);
58 }
59
60 public double MaxPanelHeight
61 {
62 get => GetValue(MaxPanelHeightProperty);
63 set => SetValue(MaxPanelHeightProperty, value);
64 }
65
66 public Thickness PanelMargin
67 {
68 get => GetValue(PanelMarginProperty);
69 set => SetValue(PanelMarginProperty, value);
70 }
71
72 public VerticalAlignment PanelVerticalAlignment
73 {
74 get => GetValue(PanelVerticalAlignmentProperty);
75 set => SetValue(PanelVerticalAlignmentProperty, value);
76 }
77
78 public IBrush? DimmerBrush
79 {
80 get => GetValue(DimmerBrushProperty);
81 set => SetValue(DimmerBrushProperty, value);
82 }
83
84 /// <summary>Клик по затемнению (не по панели). Не вызывается при <see cref="PassThroughInput"/>.</summary>
85 public event EventHandler<PointerPressedEventArgs>? DimmerPressed;
86
87 public ModalOverlay()
88 {
89 InitializeComponent();
90 PassThroughInputProperty.Changed.AddClassHandler<ModalOverlay>((o, _) => o.SyncDimmerHitTest());
91 }
92
93 protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
94 {
95 base.OnPropertyChanged(change);
96 if (change.Property == DataContextProperty || change.Property == ChildProperty)
97 SyncChildDataContext();
98 }
99
100 protected override void OnLoaded(RoutedEventArgs e)
101 {
102 base.OnLoaded(e);
103 SyncDimmerHitTest();
104 SyncChildDataContext();
105 }
106
107 /// <summary>
108 /// Логическое содержимое задаётся в <see cref="Child"/> и попадает в шаблон через <c>ContentPresenter</c>;
109 /// у корневого <see cref="Child"/> иначе может не быть того же DataContext, что у окна — биндинги «молчат».
110 /// </summary>
111 private void SyncChildDataContext()
112 {
113 if (Child is Control c)
114 c.DataContext = DataContext;
115 }
116
117 private void SyncDimmerHitTest()
118 {
119 if (Dimmer is null)
120 return;
121 Dimmer.IsHitTestVisible = !PassThroughInput;
122 }
123
124 private void OnDimmerPointerPressed(object? sender, PointerPressedEventArgs e)
125 {
126 if (PassThroughInput)
127 return;
128 DimmerPressed?.Invoke(this, e);
129 }
130
131 private void OnPanelPointerPressed(object? sender, PointerPressedEventArgs e) =>
132 e.Handled = true;
133}
134
View only · write via MCP/CIDE