Доработан Docking

This commit is contained in:
2026-01-27 05:17:35 +03:00
parent 33abd94f6e
commit 584df249f6
99 changed files with 2270 additions and 12792 deletions

View File

@@ -1,8 +1,4 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.DragDrop.Abstractions;
using Lattice.Core.DragDrop.Enums;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -14,32 +10,25 @@ namespace Lattice.Core.Docking.Models;
/// элементом для создания сложных макетов с разделителями.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="DockGroup"/> реализует как <see cref="IDragSource"/> (для
/// возможности перетаскивания всей группы), так и <see cref="IDropTarget"/>
/// (для возможности сброса на группу), что делает его полностью интегрированным
/// в систему перетаскивания док-системы.
/// </para>
/// <para>
/// Каждая группа содержит два дочерних элемента (<see cref="First"/> и
/// <see cref="Second"/>), которые могут быть либо другими группами (для
/// создания вложенной структуры), либо листами (<see cref="DockLeaf"/>)
/// с контентом. Направление разделения определяется свойством
/// <see cref="Orientation"/>.
/// </para>
/// Каждая группа содержит два дочерних элемента (<see cref="First"/> и <see cref="Second"/>),
/// которые могут быть либо другими группами (для создания вложенной структуры),
/// либо листами (<see cref="DockLeaf"/>) с контентом.
/// Направление разделения определяется свойством <see cref="Orientation"/>.
/// </remarks>
public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyPropertyChanged
public class DockGroup : IDockElement, INotifyPropertyChanged
{
/// <summary>
/// Событие, возникающее при изменении значения свойства.
/// Происходит при изменении значения свойства.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
private double _splitRatio = 0.5;
private string _id;
private IDockElement _first;
private IDockElement _second;
/// <summary>
/// Получает уникальный идентификатор группы.
/// Получает или задает уникальный идентификатор группы.
/// </summary>
/// <value>
/// Строковый идентификатор, уникальный в пределах дерева компоновки.
@@ -86,7 +75,19 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty
/// При установке нового значения автоматически обновляется свойство
/// <see cref="Parent"/> у дочернего элемента.
/// </remarks>
public IDockElement First { get; set; }
public IDockElement First
{
get => _first;
set
{
if (_first != value)
{
_first = value ?? throw new ArgumentNullException(nameof(value));
_first.Parent = this;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает второй дочерний элемент (правую или нижнюю область).
@@ -101,7 +102,19 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty
/// При установке нового значения автоматически обновляется свойство
/// <see cref="Parent"/> у дочернего элемента.
/// </remarks>
public IDockElement Second { get; set; }
public IDockElement Second
{
get => _second;
set
{
if (_second != value)
{
_second = value ?? throw new ArgumentNullException(nameof(value));
_second.Parent = this;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает направление разделения данной группы.
@@ -111,12 +124,10 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty
/// как разделена область: горизонтально или вертикально.
/// </value>
/// <remarks>
/// <para>
/// <see cref="SplitDirection.Horizontal"/> создает левую и правую области.
/// </para>
/// <para>
/// <see cref="SplitDirection.Vertical"/> создает верхнюю и нижнюю области.
/// </para>
/// <list type="bullet">
/// <item><see cref="SplitDirection.Horizontal"/> создает левую и правую области</item>
/// <item><see cref="SplitDirection.Vertical"/> создает верхнюю и нижнюю области</item>
/// </list>
/// </remarks>
public SplitDirection Orientation { get; set; }
@@ -218,7 +229,8 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty
/// у дочерних элементов на текущую группу и генерирует уникальный идентификатор,
/// если он не был предоставлен.
/// </remarks>
public DockGroup(IDockElement first, IDockElement second, SplitDirection orientation, string? id = null)
public DockGroup(IDockElement first, IDockElement second,
SplitDirection orientation, string? id = null)
{
First = first ?? throw new ArgumentNullException(nameof(first));
Second = second ?? throw new ArgumentNullException(nameof(second));
@@ -239,206 +251,4 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#region Реализация IDragSource
/// <summary>
/// Определяет, может ли группа начать операцию перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// При успешном возврате содержит информацию о перетаскивании;
/// в противном случае — null.
/// </param>
/// <returns>
/// true, если группа может начать перетаскивание; в противном случае — false.
/// </returns>
/// <remarks>
/// <para>
/// Группа может быть перетащена только если она не является корневым
/// элементом дерева (имеет родителя).
/// </para>
/// <para>
/// При успешной проверке метод заполняет <paramref name="dragInfo"/>
/// данными типа <see cref="DockElementDragData"/>.
/// </para>
/// </remarks>
public bool CanStartDrag(out DragInfo? dragInfo)
{
dragInfo = null;
// DockGroup можно перетаскивать только если он не является корневым элементом
if (Parent == null)
return false;
// Создаем данные для перетаскивания
var data = new DockElementDragData
{
ElementId = Id,
ElementType = GetType().Name,
IsGroup = true
};
dragInfo = new DragInfo(data, DragDropEffects.Move, Point.Zero, this);
return true;
}
/// <summary>
/// Начинает операцию перетаскивания для группы.
/// </summary>
/// <param name="dragInfo">
/// Информация о перетаскивании, полученная из <see cref="CanStartDrag"/>.
/// </param>
/// <returns>
/// Всегда возвращает true, так как группа не требует специальной подготовки
/// для начала перетаскивания.
/// </returns>
/// <remarks>
/// Для <see cref="DockGroup"/> этот метод не выполняет дополнительных действий,
/// так как все необходимые данные уже содержатся в <paramref name="dragInfo"/>.
/// </remarks>
public bool StartDrag(DragInfo dragInfo)
{
// DockGroup не требует дополнительной подготовки для перетаскивания
return true;
}
/// <summary>
/// Вызывается при завершении операции перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// Исходная информация о перетаскивании.
/// </param>
/// <param name="effects">
/// Эффекты, которые были применены при сбросе.
/// </param>
/// <remarks>
/// <para>
/// Этот метод вызывается после того, как операция перетаскивания была
/// завершена (успешно или неуспешно).
/// </para>
/// <para>
/// Для <see cref="DockGroup"/> этот метод не выполняет действий, так как
/// все изменения в структуре дерева уже обработаны <see cref="LayoutManager"/>.
/// </para>
/// </remarks>
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
{
// Если группа была перемещена, ничего не делаем - LayoutManager уже обработал изменение
}
/// <summary>
/// Вызывается при отмене операции перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// Исходная информация о перетаскивании.
/// </param>
/// <remarks>
/// Для <see cref="DockGroup"/> отмена перетаскивания не требует специальных
/// действий, так как структура дерева не была изменена.
/// </remarks>
public void DragCancelled(DragInfo dragInfo)
{
// Отмена перетаскивания не требует действий
}
#endregion
#region Реализация IDropTarget
/// <summary>
/// Определяет, может ли группа принять сбрасываемые данные.
/// </summary>
/// <param name="dropInfo">
/// Информация о потенциальном сбросе.
/// </param>
/// <returns>
/// true, если группа может принять данные; в противном случае — false.
/// </returns>
/// <remarks>
/// <para>
/// Группа может принимать только данные типа <see cref="DockElementDragData"/>
/// для элементов док-системы (<see cref="DockGroup"/> или <see cref="DockLeaf"/>).
/// </para>
/// <para>
/// Группа не может принять сброс самой себя (проверяется по идентификатору).
/// </para>
/// </remarks>
public bool CanAcceptDrop(DropInfo dropInfo)
{
if (dropInfo.Data is not DockElementDragData dragData)
return false;
// Нельзя сбросить элемент на самого себя
if (dragData.ElementId == Id)
return false;
// Можно принимать только элементы док-системы
return dragData.ElementType == nameof(DockGroup) || dragData.ElementType == nameof(DockLeaf);
}
/// <summary>
/// Вызывается, когда перетаскиваемый объект находится над группой.
/// </summary>
/// <param name="dropInfo">
/// Информация о текущем положении перетаскивания.
/// </param>
/// <remarks>
/// <para>
/// Этот метод вызывается постоянно, пока пользователь перемещает объект
/// над целью. Для группы он устанавливает предлагаемые эффекты в
/// <see cref="DropInfo.SuggestedEffects"/>.
/// </para>
/// <para>
/// Если группа может принять сброс, предлагается эффект перемещения;
/// в противном случае эффекты не предлагаются.
/// </para>
/// </remarks>
public void DragOver(DropInfo dropInfo)
{
if (CanAcceptDrop(dropInfo))
{
dropInfo.SuggestedEffects = DragDropEffects.Move;
}
else
{
dropInfo.SuggestedEffects = DragDropEffects.None;
}
}
/// <summary>
/// Вызывается, когда пользователь сбрасывает данные на группу.
/// </summary>
/// <param name="dropInfo">
/// Информация о сбросе.
/// </param>
/// <remarks>
/// <para>
/// Для <see cref="DockGroup"/> обработка сброса делегируется
/// <see cref="LayoutManager"/>, поэтому метод просто помечает операцию
/// как обработанную.
/// </para>
/// <para>
/// Фактическое изменение структуры дерева выполняется менеджером макета
/// на основе данных из <paramref name="dropInfo"/>.
/// </para>
/// </remarks>
public void Drop(DropInfo dropInfo)
{
// Обработка сброса делегируется LayoutManager
dropInfo.MarkAsHandled();
}
/// <summary>
/// Вызывается, когда перетаскиваемый объект покидает область группы.
/// </summary>
/// <remarks>
/// Для группы этот метод не выполняет действий, так как очистка визуальной
/// обратной связи выполняется в UI-слое.
/// </remarks>
public void DragLeave()
{
// Очистка визуальной обратной связи (будет выполнена в UI слое)
}
#endregion
}