383 lines
11 KiB
C#
383 lines
11 KiB
C#
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 Microsoft.UI.Xaml.Media;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Runtime.CompilerServices;
|
||
|
||
namespace Lattice.UI;
|
||
|
||
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 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;
|
||
}
|
||
|
||
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));
|
||
}
|
||
}
|
||
|
||
public bool IsSelected
|
||
{
|
||
get => _isSelected;
|
||
set
|
||
{
|
||
if (_isSelected == value) return;
|
||
_isSelected = value;
|
||
OnPropertyChanged(nameof(IsSelected));
|
||
}
|
||
}
|
||
|
||
public bool IsActive
|
||
{
|
||
get => _isActive;
|
||
set
|
||
{
|
||
if (_isActive == value) return;
|
||
_isActive = value;
|
||
OnPropertyChanged(nameof(IsActive));
|
||
}
|
||
}
|
||
|
||
public bool CanDrag
|
||
{
|
||
get => _canDrag;
|
||
set
|
||
{
|
||
if (_canDrag == value) return;
|
||
_canDrag = value;
|
||
OnPropertyChanged(nameof(CanDrag));
|
||
}
|
||
}
|
||
|
||
public bool CanDrop
|
||
{
|
||
get => _canDrop;
|
||
set
|
||
{
|
||
if (_canDrop == value) return;
|
||
_canDrop = value;
|
||
OnPropertyChanged(nameof(CanDrop));
|
||
}
|
||
}
|
||
|
||
public IEnumerable<IFloatingWindowControl> FloatingWindows => _floatingWindows;
|
||
public IEnumerable<IAutoHidePanelControl> AutoHidePanels => _autoHidePanels;
|
||
|
||
public bool ShowToolbox
|
||
{
|
||
get => _showToolbox;
|
||
set
|
||
{
|
||
if (_showToolbox == value) return;
|
||
_showToolbox = value;
|
||
OnPropertyChanged(nameof(ShowToolbox));
|
||
}
|
||
}
|
||
|
||
public bool ShowStatusBar
|
||
{
|
||
get => _showStatusBar;
|
||
set
|
||
{
|
||
if (_showStatusBar == value) return;
|
||
_showStatusBar = value;
|
||
OnPropertyChanged(nameof(ShowStatusBar));
|
||
}
|
||
}
|
||
|
||
public bool ShowMenu
|
||
{
|
||
get => _showMenu;
|
||
set
|
||
{
|
||
if (_showMenu == value) return;
|
||
_showMenu = value;
|
||
OnPropertyChanged(nameof(ShowMenu));
|
||
}
|
||
}
|
||
|
||
public event EventHandler? LayoutChanged;
|
||
public event EventHandler<FloatingWindowCreatedEventArgs>? FloatingWindowCreated;
|
||
public event EventHandler<FloatingWindowClosedEventArgs>? FloatingWindowClosed;
|
||
public event PropertyChangedEventHandler? PropertyChanged;
|
||
|
||
/// <inheritdoc/>
|
||
public FrameworkElement? DragDropElement => this;
|
||
|
||
/// <inheritdoc/>
|
||
public void SetupDragDropHandlers()
|
||
{
|
||
this.AllowDrop = true;
|
||
this.CanDrag = true;
|
||
|
||
// Настройка обработчиков для хоста
|
||
this.Drop += OnHostDrop;
|
||
this.DragOver += OnHostDragOver;
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void StartDrag() { /* Реализация */ }
|
||
|
||
/// <inheritdoc/>
|
||
public void EndDrag() { /* Реализация */ }
|
||
|
||
private void OnHostDragOver(object sender, DragEventArgs args)
|
||
{
|
||
args.AcceptedOperation = CanDrop ?
|
||
Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move :
|
||
Windows.ApplicationModel.DataTransfer.DataPackageOperation.None;
|
||
args.DragUIOverride.IsGlyphVisible = true;
|
||
args.DragUIOverride.Caption = "Закрепить здесь";
|
||
}
|
||
|
||
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;
|
||
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 = Lattice.UI.Docking.LatticeUIFramework.ControlFactory;
|
||
if (factory != null)
|
||
{
|
||
var control = factory.CreateControlForElement(_model);
|
||
_rootContainer.Content = control;
|
||
}
|
||
}
|
||
}
|
||
|
||
public IFloatingWindowControl CreateFloatingWindow(IDockElement element, string title)
|
||
{
|
||
throw new NotImplementedException("Floating windows not implemented yet");
|
||
}
|
||
|
||
public void CloseFloatingWindow(IFloatingWindowControl window)
|
||
{
|
||
if (_floatingWindows.Remove(window))
|
||
{
|
||
FloatingWindowClosed?.Invoke(this, new FloatingWindowClosedEventArgs(window));
|
||
}
|
||
}
|
||
|
||
public IAutoHidePanelControl AddAutoHidePanel(Core.Docking.Abstractions.IDockContent content, DockSide side)
|
||
{
|
||
if (_layoutManager != null)
|
||
{
|
||
var panel = _layoutManager.AddAutoHidePanel(content, side);
|
||
throw new NotImplementedException("Auto-hide panels not implemented yet");
|
||
}
|
||
throw new InvalidOperationException("LayoutManager is not set");
|
||
}
|
||
|
||
public void RemoveAutoHidePanel(IAutoHidePanelControl panel)
|
||
{
|
||
throw new NotImplementedException("Auto-hide panels not implemented yet");
|
||
}
|
||
|
||
public object? PrepareDragData() => Model;
|
||
public bool HandleDrop(object data, DockPosition position) => false;
|
||
|
||
public void Refresh() => UpdateRootContent();
|
||
|
||
public void ApplyTheme(IDockTheme theme)
|
||
{
|
||
// TODO: Реализовать применение темы
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
private DockPosition GetDropPosition(Windows.Foundation.Point point)
|
||
{
|
||
if (ActualWidth <= 0 || ActualHeight <= 0)
|
||
return DockPosition.Center;
|
||
|
||
var relativeX = point.X / ActualWidth;
|
||
var relativeY = point.Y / ActualHeight;
|
||
|
||
// Определяем регионы для докирования
|
||
const double edgeThreshold = 0.2; // 20% от краев
|
||
const double centerThreshold = 0.4; // Центральная область
|
||
|
||
// Проверяем края
|
||
if (relativeX < edgeThreshold) return DockPosition.Left;
|
||
if (relativeX > (1 - edgeThreshold)) return DockPosition.Right;
|
||
if (relativeY < edgeThreshold) return DockPosition.Top;
|
||
if (relativeY > (1 - edgeThreshold)) return DockPosition.Bottom;
|
||
|
||
// Если в центральной области
|
||
if (relativeX > centerThreshold && relativeX < (1 - centerThreshold) &&
|
||
relativeY > centerThreshold && relativeY < (1 - centerThreshold))
|
||
{
|
||
return DockPosition.Center;
|
||
}
|
||
|
||
// По умолчанию - центр
|
||
return DockPosition.Center;
|
||
}
|
||
|
||
private void OnHostDrop(object sender, DragEventArgs args)
|
||
{
|
||
if (CanDrop && args.DataView.Properties.TryGetValue("LatticeDockElement", out var data))
|
||
{
|
||
// Получаем позицию сброса
|
||
var position = GetDropPosition(args.GetPosition(this));
|
||
|
||
// Определяем целевой элемент
|
||
IDockElement? target = null;
|
||
if (args.OriginalSource is FrameworkElement element)
|
||
{
|
||
// Находим соответствующий контрол докинга
|
||
var dockControl = FindDockControl(element);
|
||
target = dockControl?.Model;
|
||
}
|
||
|
||
// Если цель не найдена, используем корневой элемент
|
||
target ??= LayoutManager?.Root;
|
||
|
||
if (data is IDockElement source && target != null)
|
||
{
|
||
LayoutManager?.Move(source, target, position);
|
||
}
|
||
}
|
||
}
|
||
|
||
private IDockControl? FindDockControl(FrameworkElement element)
|
||
{
|
||
// Поднимаемся по дереву элементов, чтобы найти контрол докинга
|
||
var current = element;
|
||
while (current != null)
|
||
{
|
||
if (current is IDockControl dockControl)
|
||
return dockControl;
|
||
|
||
current = VisualTreeHelper.GetParent(current) as FrameworkElement;
|
||
}
|
||
return null;
|
||
}
|
||
} |