using Lattice.Core.Abstractions; using Lattice.Core.Models; using Lattice.UI.Primitives; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace Lattice.UI.Controls; /// /// Корневой контрол Lattice, отвечающий за отображение и управление макетом докинга. /// public class LatticeDockHost : Control { public DockAnchorOverlay? AnchorOverlay => GetTemplateChild("AnchorOverlay") as DockAnchorOverlay; /// /// Определяет свойство зависимости для LayoutService. /// public static readonly DependencyProperty ServiceProperty = DependencyProperty.Register(nameof(Service), typeof(ILayoutService), typeof(LatticeDockHost), new PropertyMetadata(null, OnServiceChanged)); /// /// Сервис управления макетом, привязанный к данному хосту. /// public ILayoutService? Service { get => (ILayoutService?)GetValue(ServiceProperty); set => SetValue(ServiceProperty, value); } /// /// Указывает конкретный узел, который должен стать корнем для этого хоста. /// Если null — используется Service.Root. /// public static readonly DependencyProperty RootNodeProperty = DependencyProperty.Register(nameof(RootNode), typeof(LayoutNode), typeof(LatticeDockHost), new PropertyMetadata(null, OnServiceChanged)); public LayoutNode? RootNode { get => (LayoutNode?)GetValue(RootNodeProperty); set => SetValue(RootNodeProperty, value); } public LatticeDockHost() { this.DefaultStyleKey = typeof(LatticeDockHost); } private static void OnServiceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is LatticeDockHost host) { // Отписываемся от событий старого менеджера (если он был) if (e.OldValue is ILayoutService oldService) { oldService.LayoutUpdated -= host.OnLayoutUpdated; } // Подписываемся на новый менеджер if (e.NewValue is ILayoutService newService) { newService.LayoutUpdated += host.OnLayoutUpdated; host.RebuildUI(); } } } /// /// Именованный метод для обработки обновления макета. /// Позволяет корректно отписываться от событий и избегать утечек памяти. /// private void OnLayoutUpdated(object? sender, EventArgs e) { // WinUI 3 требует обновления UI только из основного потока this.DispatcherQueue.TryEnqueue(() => { this.RebuildUI(); }); } /// /// Полностью перестраивает визуальное дерево на основе текущего состояния Core-движка. /// private void RebuildUI() { if (this.GetTemplateChild("LayoutPresenter") is ContentPresenter presenter) { // Приоритет: сначала проверяем локальный RootNode, затем глобальный Service.Root var effectiveRoot = RootNode ?? Service?.Root; if (effectiveRoot != null) { var rootPanel = new LayoutPanel(this); rootPanel.Build(effectiveRoot); presenter.Content = rootPanel; } else { presenter.Content = null; } } } }