using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Models;
using Lattice.UI.Docking;
using Lattice.UI.Docking.Abstractions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Lattice.UI;
///
/// Представляет главный контейнер док-системы для WinUI, который служит корневым элементом
/// пользовательского интерфейса для размещения всех компонентов системы докинга.
/// Этот контрол управляет всем макетом приложения, включая основное дерево компоновки,
/// плавающие окна и автоскрываемые панели.
///
///
///
/// является центральным координатором UI-слоя док-системы,
/// интегрирующим функциональность менеджера макета, системы перетаскивания и контекстных меню.
/// Он обеспечивает согласованное отображение всех элементов и обрабатывает пользовательские
/// взаимодействия на верхнем уровне.
///
///
/// Контрол реализует интерфейс и предоставляет полный набор методов
/// для управления структурой док-системы, включая создание/закрытие плавающих окон и
/// добавление/удаление автоскрываемых панелей.
///
///
public sealed class LatticeDockHost : Control, IDockHost, IDisposable
{
private readonly PropertyChangedEventHandler _modelPropertyChangedHandler;
private readonly ObservableCollection _floatingWindows = new();
private readonly ObservableCollection _autoHidePanels = new();
private bool _disposed;
private IDockElement? _model;
private LayoutManager? _layoutManager;
private IDockContextManager? _contextManager;
private bool _isSelected;
private bool _isActive;
private bool _canDrag = true;
private bool _canDrop = true;
private bool _showToolbox = true;
private bool _showStatusBar = true;
private bool _showMenu = true;
private ContentControl? _rootContainer;
///
/// Инициализирует новый экземпляр класса .
///
///
/// Конструктор устанавливает ключ стиля по умолчанию, инициализирует обработчик изменений модели
/// и подписывается на событие изменения контекста данных.
///
public LatticeDockHost()
{
this.DefaultStyleKey = typeof(LatticeDockHost);
_modelPropertyChangedHandler = OnModelPropertyChanged;
this.DataContextChanged += OnDataContextChanged;
}
///
/// Получает или задает модель данных, связанную с этим контролом.
///
///
/// Экземпляр, реализующий , представляющий корневой элемент
/// дерева компоновки. Может быть null.
///
///
/// Этот элемент является корнем всего макета док-системы. При изменении этого свойства
/// происходит перестройка всего пользовательского интерфейса.
///
public IDockElement? Model
{
get => _model;
set
{
if (_model == value) return;
DetachModel();
_model = value;
AttachModel();
OnPropertyChanged(nameof(Model));
}
}
///
/// Получает или задает менеджер макета, к которому принадлежит этот контрол.
///
///
/// Экземпляр , управляющий структурой док-системы.
///
///
/// Менеджер макета используется для выполнения операций с деревом компоновки
/// и координации изменений между различными элементами системы.
///
public LayoutManager? LayoutManager
{
get => _layoutManager;
set
{
if (_layoutManager == value) return;
_layoutManager = value;
OnPropertyChanged(nameof(LayoutManager));
}
}
///
/// Получает или задает контекстный менеджер для этого контрола.
///
///
/// Экземпляр , управляющий контекстными меню и действиями.
///
///
/// Контекстный менеджер используется для отображения меню, связанных с этим элементом,
/// и выполнения команд, доступных в текущем контексте.
///
public IDockContextManager? ContextManager
{
get => _contextManager;
set
{
if (_contextManager == value) return;
_contextManager = value;
OnPropertyChanged(nameof(ContextManager));
}
}
///
/// Получает или задает признак того, что контрол выбран.
///
///
/// true, если контрол выбран; в противном случае — false.
///
///
/// Выделение контрола обычно визуально выделяет его границы или фон,
/// чтобы указать пользователю на активный элемент.
///
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value) return;
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
///
/// Получает или задает признак того, что контрол активен.
///
///
/// true, если контрол активен; в противном случае — false.
///
///
/// Активный контрол обычно получает фокус ввода и может обрабатывать команды клавиатуры.
///
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
OnPropertyChanged(nameof(IsActive));
}
}
///
/// Получает или задает признак того, что контрол можно перетаскивать.
///
///
/// true, если контрол можно перетаскивать; в противном случае — false.
///
///
/// Этот флаг влияет на возможность инициирования операции перетаскивания
/// при взаимодействии пользователя с этим контролом.
///
public bool CanDrag
{
get => _canDrag;
set
{
if (_canDrag == value) return;
_canDrag = value;
OnPropertyChanged(nameof(CanDrag));
}
}
///
/// Получает или задает признак того, что контрол может принимать сброс.
///
///
/// true, если контрол может принимать сброс; в противном случае — false.
///
///
/// Этот флаг влияет на возможность завершения операции перетаскивания
/// сбросом данных на этот контрол.
///
public bool CanDrop
{
get => _canDrop;
set
{
if (_canDrop == value) return;
_canDrop = value;
OnPropertyChanged(nameof(CanDrop));
}
}
///
/// Получает коллекцию контролов плавающих окон, связанных с этим хостом.
///
///
/// Коллекция объектов, реализующих ,
/// представляющих все активные плавающие окна в системе.
///
///
/// Коллекция является наблюдаемой (ObservableCollection), что позволяет автоматически
/// обновлять пользовательский интерфейс при добавлении или удалении окон.
///
public IEnumerable FloatingWindows => _floatingWindows;
///
/// Получает коллекцию контролов автоскрываемых панелей, прикрепленных к краям окна.
///
///
/// Коллекция объектов, реализующих ,
/// представляющих автоскрываемые панели на разных сторонах окна.
///
///
/// Коллекция является наблюдаемой (ObservableCollection), что позволяет автоматически
/// обновлять пользовательский интерфейс при добавлении или удалении панелей.
///
public IEnumerable AutoHidePanels => _autoHidePanels;
///
/// Получает или задает значение, указывающее, отображается ли панель инструментов (Toolbox).
///
///
/// true, если панель инструментов видима; в противном случае — false.
///
///
/// Панель инструментов обычно содержит элементы для быстрого доступа к командам
/// или создания новых компонентов в приложении.
///
public bool ShowToolbox
{
get => _showToolbox;
set
{
if (_showToolbox == value) return;
_showToolbox = value;
OnPropertyChanged(nameof(ShowToolbox));
}
}
///
/// Получает или задает значение, указывающее, отображается ли строка состояния.
///
///
/// true, если строка состояния видима; в противном случае — false.
///
///
/// Строка состояния обычно отображает текущий статус приложения,
/// информацию о выбранном элементе или прогресс выполнения операций.
///
public bool ShowStatusBar
{
get => _showStatusBar;
set
{
if (_showStatusBar == value) return;
_showStatusBar = value;
OnPropertyChanged(nameof(ShowStatusBar));
}
}
///
/// Получает или задает значение, указывающее, отображается ли главное меню приложения.
///
///
/// true, если главное меню видимо; в противном случае — false.
///
///
/// Главное меню содержит основные команды приложения, организованные в иерархическую структуру.
///
public bool ShowMenu
{
get => _showMenu;
set
{
if (_showMenu == value) return;
_showMenu = value;
OnPropertyChanged(nameof(ShowMenu));
}
}
///
/// Событие, возникающее при изменении структуры макета док-системы.
///
///
/// Может вызываться при добавлении/удалении элементов, изменении размеров,
/// создании/закрытии плавающих окон и других операциях, влияющих на компоновку.
///
public event EventHandler? LayoutChanged;
///
/// Событие, возникающее при создании нового плавающего окна.
///
public event EventHandler? FloatingWindowCreated;
///
/// Событие, возникающее при закрытии плавающего окна.
///
public event EventHandler? FloatingWindowClosed;
///
/// Событие, возникающее при изменении значения свойства.
///
public event PropertyChangedEventHandler? PropertyChanged;
///
/// Вызывается при применении шаблона контрола.
///
///
/// Метод получает ссылки на именованные части шаблона и обновляет отображение
/// корневого содержимого в соответствии с текущим состоянием модели.
///
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_rootContainer = GetTemplateChild("PART_RootContainer") as ContentControl;
UpdateRootContent();
}
private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
Model = args.NewValue as IDockElement;
}
private void AttachModel()
{
if (_model != null && _layoutManager != null)
{
// Подписываемся на события менеджера макета
_layoutManager.LayoutUpdated += OnLayoutUpdated;
_layoutManager.AutoHidePanelsChanged += OnAutoHidePanelsChanged;
// Устанавливаем DataContext
this.DataContext = _model;
UpdateRootContent();
}
}
private void DetachModel()
{
if (_model != null && _layoutManager != null)
{
// Отписываемся от событий
_layoutManager.LayoutUpdated -= OnLayoutUpdated;
_layoutManager.AutoHidePanelsChanged -= OnAutoHidePanelsChanged;
this.DataContext = null;
}
}
private void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// Обработка изменений модели
OnPropertyChanged(e.PropertyName);
}
private void OnLayoutUpdated()
{
UpdateRootContent();
LayoutChanged?.Invoke(this, EventArgs.Empty);
}
private void OnAutoHidePanelsChanged(object? sender, EventArgs e)
{
// Обновление автоскрываемых панелей
OnPropertyChanged(nameof(AutoHidePanels));
}
private void UpdateRootContent()
{
if (_rootContainer != null && _model != null && _layoutManager != null)
{
// Создаем дерево контролов через фабрику
var factory = LatticeUIFramework.ControlFactory;
if (factory != null)
{
var control = factory.CreateControlForElement(_model);
_rootContainer.Content = control;
}
}
}
///
/// Создает новое плавающее окно для размещения указанного элемента док-системы.
///
///
/// Элемент док-системы (группа или лист), который будет размещен в плавающем окне.
///
/// Заголовок создаваемого окна.
///
/// Экземпляр , представляющий созданное плавающее окно.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, так как метод еще не реализован.
///
///
/// Созданное окно может быть перемещено пользователем в любое место экрана,
/// изменено в размерах и обычно содержит стандартные элементы управления окном
/// (заголовок, кнопки закрытия/сворачивания).
///
public IFloatingWindowControl CreateFloatingWindow(IDockElement element, string title)
{
if (element == null) throw new ArgumentNullException(nameof(element));
// TODO: Реализовать создание плавающего окна через фабрику
throw new NotImplementedException();
}
///
/// Закрывает указанное плавающее окно и возвращает его содержимое в основной макет.
///
///
/// Плавающее окно, которое необходимо закрыть.
///
///
/// Выбрасывается, если равен null.
///
///
/// При закрытии плавающего окна его содержимое обычно возвращается в то место
/// в основном макете, откуда оно было извлечено, или в ближайшую допустимую позицию.
///
public void CloseFloatingWindow(IFloatingWindowControl window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (_floatingWindows.Remove(window))
{
FloatingWindowClosed?.Invoke(this, new FloatingWindowClosedEventArgs(window));
}
}
///
/// Добавляет автоскрываемую панель с указанным содержимым к заданной стороне окна.
///
///
/// Контент, который будет отображаться в автоскрываемой панели.
///
///
/// Сторона окна, к которой будет прикреплена панель.
///
///
/// Экземпляр , представляющий созданную панель.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, если свойство не установлено.
///
///
/// Выбрасывается, так как метод еще не реализован.
///
///
/// Автоскрываемые панели полезны для инструментов, к которым нужен частый,
/// но не постоянный доступ, так как они экономят пространство экрана.
///
public IAutoHidePanelControl AddAutoHidePanel(Core.Docking.Abstractions.IDockContent content, DockSide side)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (_layoutManager != null)
{
var panel = _layoutManager.AddAutoHidePanel(content, side);
// TODO: Создать UI-контрол для автоскрываемой панели через фабрику
throw new NotImplementedException();
}
throw new InvalidOperationException("LayoutManager is not set");
}
///
/// Удаляет автоскрываемую панель из интерфейса.
///
///
/// Автоскрываемая панель, которую необходимо удалить.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, так как метод еще не реализован.
///
///
/// После удаления панели её содержимое обычно либо закрывается полностью,
/// либо преобразуется в обычную закрепленную панель, в зависимости от настроек.
///
public void RemoveAutoHidePanel(IAutoHidePanelControl panel)
{
if (panel == null) throw new ArgumentNullException(nameof(panel));
// TODO: Реализовать удаление автоскрываемой панели
throw new NotImplementedException();
}
///
/// Обновляет внешний вид контрола в соответствии с текущим состоянием модели.
///
///
/// Вызывает обновление корневого содержимого и всех дочерних элементов.
///
public void Refresh()
{
UpdateRootContent();
}
///
/// Применяет указанную тему к контролу.
///
/// Тема для применения.
///
/// В текущей реализации метод является заглушкой и должен быть расширен
/// для поддержки динамического изменения тем.
///
public void ApplyTheme(IDockTheme theme)
{
// Применение темы к контролу
if (theme != null)
{
// TODO: Реализовать применение темы к стилям контрола
}
}
///
/// Вызывается при изменении состояния модели для обновления UI.
///
/// Имя изменившегося свойства модели.
///
/// Перенаправляет вызов в обработчик изменений модели.
///
public void OnModelPropertyChanged(string propertyName)
{
if (_model != null)
{
OnModelPropertyChanged(_model, new PropertyChangedEventArgs(propertyName));
}
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
///
/// Освобождает ресурсы, используемые этим экземпляром контрола.
///
///
/// Выполняет отписку от событий модели, очистку коллекций и освобождение ресурсов.
///
public void Dispose()
{
if (!_disposed)
{
DetachModel();
// Очищаем коллекции
_floatingWindows.Clear();
_autoHidePanels.Clear();
_disposed = true;
GC.SuppressFinalize(this);
}
}
}