Files
Lattice/Lattice.UI.Docking.WinUI/Controls/LatticeDockHost.cs
2026-01-18 16:33:35 +03:00

611 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Models;
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;
/// <summary>
/// Представляет главный контейнер док-системы для WinUI, который служит корневым элементом
/// пользовательского интерфейса для размещения всех компонентов системы докинга.
/// Этот контрол управляет всем макетом приложения, включая основное дерево компоновки,
/// плавающие окна и автоскрываемые панели.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="LatticeDockHost"/> является центральным координатором UI-слоя док-системы,
/// интегрирующим функциональность менеджера макета, системы перетаскивания и контекстных меню.
/// Он обеспечивает согласованное отображение всех элементов и обрабатывает пользовательские
/// взаимодействия на верхнем уровне.
/// </para>
/// <para>
/// Контрол реализует интерфейс <see cref="IDockHost"/> и предоставляет полный набор методов
/// для управления структурой док-системы, включая создание/закрытие плавающих окон и
/// добавление/удаление автоскрываемых панелей.
/// </para>
/// </remarks>
public sealed class LatticeDockHost : Control, IDockHost, IDisposable
{
private readonly PropertyChangedEventHandler _modelPropertyChangedHandler;
private readonly ObservableCollection<IFloatingWindowControl> _floatingWindows = new();
private readonly ObservableCollection<IAutoHidePanelControl> _autoHidePanels = new();
private bool _disposed;
private IDockElement? _model;
private LayoutManager? _layoutManager;
private IDockDragDropService? _dragDropService;
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;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="LatticeDockHost"/>.
/// </summary>
/// <remarks>
/// Конструктор устанавливает ключ стиля по умолчанию, инициализирует обработчик изменений модели
/// и подписывается на событие изменения контекста данных.
/// </remarks>
public LatticeDockHost()
{
this.DefaultStyleKey = typeof(LatticeDockHost);
_modelPropertyChangedHandler = OnModelPropertyChanged;
this.DataContextChanged += OnDataContextChanged;
}
/// <summary>
/// Получает или задает модель данных, связанную с этим контролом.
/// </summary>
/// <value>
/// Экземпляр, реализующий <see cref="IDockElement"/>, представляющий корневой элемент
/// дерева компоновки. Может быть null.
/// </value>
/// <remarks>
/// Этот элемент является корнем всего макета док-системы. При изменении этого свойства
/// происходит перестройка всего пользовательского интерфейса.
/// </remarks>
public IDockElement? Model
{
get => _model;
set
{
if (_model == value) return;
DetachModel();
_model = value;
AttachModel();
OnPropertyChanged(nameof(Model));
}
}
/// <summary>
/// Получает или задает менеджер макета, к которому принадлежит этот контрол.
/// </summary>
/// <value>
/// Экземпляр <see cref="LayoutManager"/>, управляющий структурой док-системы.
/// </value>
/// <remarks>
/// Менеджер макета используется для выполнения операций с деревом компоновки
/// и координации изменений между различными элементами системы.
/// </remarks>
public LayoutManager? LayoutManager
{
get => _layoutManager;
set
{
if (_layoutManager == value) return;
_layoutManager = value;
OnPropertyChanged(nameof(LayoutManager));
}
}
/// <summary>
/// Получает или задает сервис перетаскивания, используемый этим контролом.
/// </summary>
/// <value>
/// Реализация <see cref="IDockDragDropService"/> для обработки операций перетаскивания.
/// </value>
/// <remarks>
/// Сервис перетаскивания обеспечивает взаимодействие с системой drag-and-drop,
/// включая визуальную обратную связь и обработку событий.
/// </remarks>
public IDockDragDropService? DragDropService
{
get => _dragDropService;
set
{
if (_dragDropService == value) return;
_dragDropService = value;
OnPropertyChanged(nameof(DragDropService));
}
}
/// <summary>
/// Получает или задает контекстный менеджер для этого контрола.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockContextManager"/>, управляющий контекстными меню и действиями.
/// </value>
/// <remarks>
/// Контекстный менеджер используется для отображения меню, связанных с этим элементом,
/// и выполнения команд, доступных в текущем контексте.
/// </remarks>
public IDockContextManager? ContextManager
{
get => _contextManager;
set
{
if (_contextManager == value) return;
_contextManager = value;
OnPropertyChanged(nameof(ContextManager));
}
}
/// <summary>
/// Получает или задает признак того, что контрол выбран.
/// </summary>
/// <value>
/// true, если контрол выбран; в противном случае — false.
/// </value>
/// <remarks>
/// Выделение контрола обычно визуально выделяет его границы или фон,
/// чтобы указать пользователю на активный элемент.
/// </remarks>
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value) return;
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
/// <summary>
/// Получает или задает признак того, что контрол активен.
/// </summary>
/// <value>
/// true, если контрол активен; в противном случае — false.
/// </value>
/// <remarks>
/// Активный контрол обычно получает фокус ввода и может обрабатывать команды клавиатуры.
/// </remarks>
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
OnPropertyChanged(nameof(IsActive));
}
}
/// <summary>
/// Получает или задает признак того, что контрол можно перетаскивать.
/// </summary>
/// <value>
/// true, если контрол можно перетаскивать; в противном случае — false.
/// </value>
/// <remarks>
/// Этот флаг влияет на возможность инициирования операции перетаскивания
/// при взаимодействии пользователя с этим контролом.
/// </remarks>
public bool CanDrag
{
get => _canDrag;
set
{
if (_canDrag == value) return;
_canDrag = value;
OnPropertyChanged(nameof(CanDrag));
}
}
/// <summary>
/// Получает или задает признак того, что контрол может принимать сброс.
/// </summary>
/// <value>
/// true, если контрол может принимать сброс; в противном случае — false.
/// </value>
/// <remarks>
/// Этот флаг влияет на возможность завершения операции перетаскивания
/// сбросом данных на этот контрол.
/// </remarks>
public bool CanDrop
{
get => _canDrop;
set
{
if (_canDrop == value) return;
_canDrop = value;
OnPropertyChanged(nameof(CanDrop));
}
}
/// <summary>
/// Получает коллекцию контролов плавающих окон, связанных с этим хостом.
/// </summary>
/// <value>
/// Коллекция объектов, реализующих <see cref="IFloatingWindowControl"/>,
/// представляющих все активные плавающие окна в системе.
/// </value>
/// <remarks>
/// Коллекция является наблюдаемой (ObservableCollection), что позволяет автоматически
/// обновлять пользовательский интерфейс при добавлении или удалении окон.
/// </remarks>
public IEnumerable<IFloatingWindowControl> FloatingWindows => _floatingWindows;
/// <summary>
/// Получает коллекцию контролов автоскрываемых панелей, прикрепленных к краям окна.
/// </summary>
/// <value>
/// Коллекция объектов, реализующих <see cref="IAutoHidePanelControl"/>,
/// представляющих автоскрываемые панели на разных сторонах окна.
/// </value>
/// <remarks>
/// Коллекция является наблюдаемой (ObservableCollection), что позволяет автоматически
/// обновлять пользовательский интерфейс при добавлении или удалении панелей.
/// </remarks>
public IEnumerable<IAutoHidePanelControl> AutoHidePanels => _autoHidePanels;
/// <summary>
/// Получает или задает значение, указывающее, отображается ли панель инструментов (Toolbox).
/// </summary>
/// <value>
/// true, если панель инструментов видима; в противном случае — false.
/// </value>
/// <remarks>
/// Панель инструментов обычно содержит элементы для быстрого доступа к командам
/// или создания новых компонентов в приложении.
/// </remarks>
public bool ShowToolbox
{
get => _showToolbox;
set
{
if (_showToolbox == value) return;
_showToolbox = value;
OnPropertyChanged(nameof(ShowToolbox));
}
}
/// <summary>
/// Получает или задает значение, указывающее, отображается ли строка состояния.
/// </summary>
/// <value>
/// true, если строка состояния видима; в противном случае — false.
/// </value>
/// <remarks>
/// Строка состояния обычно отображает текущий статус приложения,
/// информацию о выбранном элементе или прогресс выполнения операций.
/// </remarks>
public bool ShowStatusBar
{
get => _showStatusBar;
set
{
if (_showStatusBar == value) return;
_showStatusBar = value;
OnPropertyChanged(nameof(ShowStatusBar));
}
}
/// <summary>
/// Получает или задает значение, указывающее, отображается ли главное меню приложения.
/// </summary>
/// <value>
/// true, если главное меню видимо; в противном случае — false.
/// </value>
/// <remarks>
/// Главное меню содержит основные команды приложения, организованные в иерархическую структуру.
/// </remarks>
public bool ShowMenu
{
get => _showMenu;
set
{
if (_showMenu == value) return;
_showMenu = value;
OnPropertyChanged(nameof(ShowMenu));
}
}
/// <summary>
/// Событие, возникающее при изменении структуры макета док-системы.
/// </summary>
/// <remarks>
/// Может вызываться при добавлении/удалении элементов, изменении размеров,
/// создании/закрытии плавающих окон и других операциях, влияющих на компоновку.
/// </remarks>
public event EventHandler? LayoutChanged;
/// <summary>
/// Событие, возникающее при создании нового плавающего окна.
/// </summary>
public event EventHandler<FloatingWindowCreatedEventArgs>? FloatingWindowCreated;
/// <summary>
/// Событие, возникающее при закрытии плавающего окна.
/// </summary>
public event EventHandler<FloatingWindowClosedEventArgs>? FloatingWindowClosed;
/// <summary>
/// Событие, возникающее при изменении значения свойства.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Вызывается при применении шаблона контрола.
/// </summary>
/// <remarks>
/// Метод получает ссылки на именованные части шаблона и обновляет отображение
/// корневого содержимого в соответствии с текущим состоянием модели.
/// </remarks>
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;
}
}
}
/// <summary>
/// Создает новое плавающее окно для размещения указанного элемента док-системы.
/// </summary>
/// <param name="element">
/// Элемент док-системы (группа или лист), который будет размещен в плавающем окне.
/// </param>
/// <param name="title">Заголовок создаваемого окна.</param>
/// <returns>
/// Экземпляр <see cref="IFloatingWindowControl"/>, представляющий созданное плавающее окно.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="element"/> равен null.
/// </exception>
/// <exception cref="NotImplementedException">
/// Выбрасывается, так как метод еще не реализован.
/// </exception>
/// <remarks>
/// Созданное окно может быть перемещено пользователем в любое место экрана,
/// изменено в размерах и обычно содержит стандартные элементы управления окном
/// (заголовок, кнопки закрытия/сворачивания).
/// </remarks>
public IFloatingWindowControl CreateFloatingWindow(IDockElement element, string title)
{
if (element == null) throw new ArgumentNullException(nameof(element));
// TODO: Реализовать создание плавающего окна через фабрику
throw new NotImplementedException();
}
/// <summary>
/// Закрывает указанное плавающее окно и возвращает его содержимое в основной макет.
/// </summary>
/// <param name="window">
/// Плавающее окно, которое необходимо закрыть.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="window"/> равен null.
/// </exception>
/// <remarks>
/// При закрытии плавающего окна его содержимое обычно возвращается в то место
/// в основном макете, откуда оно было извлечено, или в ближайшую допустимую позицию.
/// </remarks>
public void CloseFloatingWindow(IFloatingWindowControl window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (_floatingWindows.Remove(window))
{
FloatingWindowClosed?.Invoke(this, new FloatingWindowClosedEventArgs(window));
}
}
/// <summary>
/// Добавляет автоскрываемую панель с указанным содержимым к заданной стороне окна.
/// </summary>
/// <param name="content">
/// Контент, который будет отображаться в автоскрываемой панели.
/// </param>
/// <param name="side">
/// Сторона окна, к которой будет прикреплена панель.
/// </param>
/// <returns>
/// Экземпляр <see cref="IAutoHidePanelControl"/>, представляющий созданную панель.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="content"/> равен null.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Выбрасывается, если свойство <see cref="LayoutManager"/> не установлено.
/// </exception>
/// <exception cref="NotImplementedException">
/// Выбрасывается, так как метод еще не реализован.
/// </exception>
/// <remarks>
/// Автоскрываемые панели полезны для инструментов, к которым нужен частый,
/// но не постоянный доступ, так как они экономят пространство экрана.
/// </remarks>
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");
}
/// <summary>
/// Удаляет автоскрываемую панель из интерфейса.
/// </summary>
/// <param name="panel">
/// Автоскрываемая панель, которую необходимо удалить.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="panel"/> равен null.
/// </exception>
/// <exception cref="NotImplementedException">
/// Выбрасывается, так как метод еще не реализован.
/// </exception>
/// <remarks>
/// После удаления панели её содержимое обычно либо закрывается полностью,
/// либо преобразуется в обычную закрепленную панель, в зависимости от настроек.
/// </remarks>
public void RemoveAutoHidePanel(IAutoHidePanelControl panel)
{
if (panel == null) throw new ArgumentNullException(nameof(panel));
// TODO: Реализовать удаление автоскрываемой панели
throw new NotImplementedException();
}
/// <summary>
/// Обновляет внешний вид контрола в соответствии с текущим состоянием модели.
/// </summary>
/// <remarks>
/// Вызывает обновление корневого содержимого и всех дочерних элементов.
/// </remarks>
public void Refresh()
{
UpdateRootContent();
}
/// <summary>
/// Применяет указанную тему к контролу.
/// </summary>
/// <param name="theme">Тема для применения.</param>
/// <remarks>
/// В текущей реализации метод является заглушкой и должен быть расширен
/// для поддержки динамического изменения тем.
/// </remarks>
public void ApplyTheme(IDockTheme theme)
{
// Применение темы к контролу
if (theme != null)
{
// TODO: Реализовать применение темы к стилям контрола
}
}
/// <summary>
/// Вызывается при изменении состояния модели для обновления UI.
/// </summary>
/// <param name="propertyName">Имя изменившегося свойства модели.</param>
/// <remarks>
/// Перенаправляет вызов в обработчик изменений модели.
/// </remarks>
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));
}
/// <summary>
/// Освобождает ресурсы, используемые этим экземпляром контрола.
/// </summary>
/// <remarks>
/// Выполняет отписку от событий модели, очистку коллекций и освобождение ресурсов.
/// </remarks>
public void Dispose()
{
if (!_disposed)
{
DetachModel();
// Очищаем коллекции
_floatingWindows.Clear();
_autoHidePanels.Clear();
_disposed = true;
GC.SuppressFinalize(this);
}
}
}