Доработан 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

@@ -5,44 +5,81 @@ namespace Lattice.Core.Docking.Models;
/// <summary>
/// Представляет автоскрываемую панель, которая может быть прикреплена к одной из сторон окна.
/// Автоскрываемые панели скрываются, оставляя только заголовок, и появляются при наведении курсора.
/// Автоскрываемые панели скрываются, оставляя видимой только полоску-заголовок,
/// и разворачиваются при наведении курсора или клике.
/// </summary>
/// <remarks>
/// Автоскрываемые панели являются ключевым элементом интерфейса современных IDE,
/// Автоскрываемые панели являются важным элементом современных IDE-подобных приложений,
/// позволяя экономить пространство экрана при сохранении быстрого доступа к инструментам.
/// </remarks>
public class AutoHidePanel : INotifyPropertyChanged
{
/// <summary>
/// Происходит при изменении значения свойства.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private bool _isVisible = false;
private double _slideOffset = 0;
/// <summary>
/// Уникальный идентификатор автоскрываемой панели.
/// Получает уникальный идентификатор автоскрываемой панели.
/// </summary>
/// <value>
/// Строковый идентификатор, сгенерированный с помощью GUID.
/// </value>
public string Id { get; } = Guid.NewGuid().ToString();
/// <summary>
/// Содержимое панели.
/// Получает или задает содержимое панели.
/// </summary>
public Abstractions.IDockContent Content { get; set; }
/// <value>
/// Объект, реализующий <see cref="Abstractions.IDockContent"/>.
/// </value>
/// <exception cref="ArgumentNullException">
/// Выбрасывается при попытке установить значение null.
/// </exception>
public Abstractions.IDockContent Content
{
get => _content;
set
{
if (_content != value)
{
_content = value ?? throw new ArgumentNullException(nameof(value));
OnPropertyChanged();
OnPropertyChanged(nameof(Title));
}
}
}
private Abstractions.IDockContent _content;
/// <summary>
/// Сторона окна, к которой прикреплена панель.
/// Получает или задает сторону окна, к которой прикреплена панель.
/// </summary>
/// <value>
/// Значение перечисления <see cref="DockSide"/>, указывающее сторону прикрепления.
/// </value>
public DockSide Side { get; set; }
/// <summary>
/// Ширина панели (для левой/правой сторон) или высота (для верхней/нижней сторон).
/// Получает или задает ширину панели (для левой/правой сторон)
/// или высоту (для верхней/нижней сторон).
/// </summary>
/// <value>
/// Размер панели в пикселях. Значение по умолчанию: 300.
/// </value>
public double Size { get; set; } = 300;
/// <summary>
/// Признак видимости панели.
/// Получает или задает признак видимости панели.
/// </summary>
/// <value>
/// true, если панель развернута и видима; в противном случае false.
/// </value>
/// <remarks>
/// При изменении этого свойства генерируется событие <see cref="PropertyChanged"/>.
/// </remarks>
public bool IsVisible
{
get => _isVisible;
@@ -57,8 +94,14 @@ public class AutoHidePanel : INotifyPropertyChanged
}
/// <summary>
/// Смещение для анимации выезда/заезда панели (0-1).
/// Получает или задает смещение для анимации выезда/заезда панели.
/// </summary>
/// <value>
/// Значение от 0.0 до 1.0, где 0.0 - полностью скрыта, 1.0 - полностью развернута.
/// </value>
/// <remarks>
/// Используется для плавной анимации отображения/скрытия панели.
/// </remarks>
public double SlideOffset
{
get => _slideOffset;
@@ -73,15 +116,22 @@ public class AutoHidePanel : INotifyPropertyChanged
}
/// <summary>
/// Заголовок панели (обычно берется из содержимого).
/// Получает заголовок панели.
/// </summary>
/// <value>
/// Заголовок, взятый из содержимого панели.
/// Если содержимое не установлено, возвращает "Auto-hide Panel".
/// </value>
public string Title => Content?.Title ?? "Auto-hide Panel";
/// <summary>
/// Инициализирует новый экземпляр автоскрываемой панели.
/// Инициализирует новый экземпляр класса <see cref="AutoHidePanel"/>.
/// </summary>
/// <param name="content">Содержимое панели.</param>
/// <param name="side">Сторона окна для прикрепления.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="content"/> равен null.
/// </exception>
public AutoHidePanel(Abstractions.IDockContent content, DockSide side)
{
Content = content ?? throw new ArgumentNullException(nameof(content));
@@ -91,6 +141,9 @@ public class AutoHidePanel : INotifyPropertyChanged
/// <summary>
/// Переключает видимость панели.
/// </summary>
/// <remarks>
/// Если панель была видимой, становится скрытой, и наоборот.
/// </remarks>
public void Toggle()
{
IsVisible = !IsVisible;
@@ -111,4 +164,15 @@ public class AutoHidePanel : INotifyPropertyChanged
{
IsVisible = false;
}
}
/// <summary>
/// Вызывает событие <see cref="PropertyChanged"/>.
/// </summary>
/// <param name="name">
/// Имя изменившегося свойства. Если не указано, определяется автоматически.
/// </param>
protected void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}

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
}

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.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -15,34 +11,24 @@ namespace Lattice.Core.Docking.Models;
/// отображаемого пользователю содержимого.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="DockLeaf"/> реализует интерфейсы <see cref="IDockContainer"/>,
/// <see cref="IDragSource"/> и <see cref="IDropTarget"/>, что позволяет ему:
/// </para>
/// <list type="bullet">
/// <item>Управлять коллекцией вкладок</item>
/// <item>Быть источником перетаскивания (как всего листа, так и отдельных вкладок)</item>
/// <item>Принимать сброс других элементов или вкладок</item>
/// </list>
/// <para>
/// Лист является основным элементом, с которым взаимодействует пользователь
/// при работе с документами или инструментальными панелями в IDE-подобных
/// приложениях.
/// </para>
/// </remarks>
public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDropTarget
public class DockLeaf : IDockContainer, INotifyPropertyChanged
{
/// <summary>
/// Событие, возникающее при изменении значения свойства.
/// Происходит при изменении значения свойства.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
private readonly ObservableCollection<IDockContent> _items = new();
private IDockContent? _activeContent;
private string _id;
private TabPlacement _tabPlacement = TabPlacement.Bottom;
/// <summary>
/// Получает уникальный идентификатор листа.
/// Получает или задает уникальный идентификатор листа.
/// </summary>
/// <value>
/// Строковый идентификатор, уникальный в пределах дерева компоновки.
@@ -88,13 +74,9 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr
/// Активная вкладка или null, если в контейнере нет вкладок.
/// </value>
/// <remarks>
/// <para>
/// При установке нового значения проверяется, что вкладка действительно
/// содержится в коллекции <see cref="Children"/>.
/// </para>
/// <para>
/// Изменение этого свойства вызывает событие <see cref="PropertyChanged"/>.
/// </para>
/// </remarks>
public IDockContent? ActiveContent
{
@@ -152,7 +134,18 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr
/// <remarks>
/// Поддерживаются все четыре стороны: верх, низ, лево, право.
/// </remarks>
public TabPlacement TabPlacement { get; set; } = TabPlacement.Bottom;
public TabPlacement TabPlacement
{
get => _tabPlacement;
set
{
if (_tabPlacement != value)
{
_tabPlacement = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DockLeaf"/>.
@@ -187,17 +180,15 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr
/// Контент для добавления.
/// </param>
/// <remarks>
/// <para>
/// Если контент уже содержится в коллекции, он не добавляется повторно,
/// но становится активным.
/// </para>
/// <para>
/// Этот метод обновляет свойство <see cref="ActiveContent"/> и вызывает
/// соответствующее событие изменения свойства.
/// </para>
/// </remarks>
public void AddContent(IDockContent content)
{
if (content == null) return;
if (!_items.Contains(content))
{
_items.Add(content);
@@ -212,18 +203,16 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr
/// Контент для удаления.
/// </param>
/// <remarks>
/// <para>
/// Если удаляемый контент является активным, автоматически выбирается
/// новая активная вкладка (следующая в списке или предыдущая, если удалена
/// последняя).
/// </para>
/// <para>
/// Если после удаления контейнер становится пустым, он может быть удален
/// из дерева макета системой компоновки.
/// </para>
/// </remarks>
public void RemoveContent(IDockContent content)
{
if (content == null) return;
int index = _items.IndexOf(content);
if (index == -1) return;
@@ -237,344 +226,4 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr
ActiveContent = null;
}
}
#region Реализация IDragSource
/// <summary>
/// Определяет, может ли лист начать операцию перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// При успешном возврате содержит информацию о перетаскивании;
/// в противном случае — null.
/// </param>
/// <returns>
/// true, если лист может начать перетаскивание; в противном случае — false.
/// </returns>
/// <remarks>
/// <para>
/// Лист может быть перетащен, если:
/// </para>
/// <list type="bullet">
/// <item>Он имеет родителя (не является корневым)</item>
/// <item>Или имеет хотя бы одну вкладку (не пустой)</item>
/// </list>
/// <para>
/// В зависимости от наличия активного контента создаются разные данные:
/// </para>
/// <list type="bullet">
/// <item>
/// Если есть активный контент - создается <see cref="ContentDragData"/>
/// для перетаскивания конкретной вкладки
/// </item>
/// <item>
/// Если нет активного контента - создается <see cref="DockElementDragData"/>
/// для перетаскивания всего листа
/// </item>
/// </list>
/// </remarks>
public bool CanStartDrag(out DragInfo? dragInfo)
{
dragInfo = null;
// DockLeaf можно перетаскивать
if (Parent == null && Children.Count == 0)
return false; // Не перетаскиваем пустые корневые листья
object data;
// Если есть активный контент, перетаскиваем контент, иначе перетаскиваем весь лист
if (ActiveContent != null)
{
data = new ContentDragData
{
ElementId = Id,
ContentId = ActiveContent.Id,
ContentTitle = ActiveContent.Title,
ContentType = ActiveContent.GetType().Name
};
}
else
{
data = new DockElementDragData
{
ElementId = Id,
ElementType = GetType().Name,
IsGroup = false,
Width = Width,
Height = Height
};
}
dragInfo = new DragInfo(data, DragDropEffects.Move | DragDropEffects.Copy, Point.Zero, this);
return true;
}
/// <summary>
/// Начинает операцию перетаскивания для листа.
/// </summary>
/// <param name="dragInfo">
/// Информация о перетаскивании.
/// </param>
/// <returns>
/// Всегда возвращает true.
/// </returns>
/// <remarks>
/// Для <see cref="DockLeaf"/> этот метод не выполняет дополнительных действий.
/// </remarks>
public bool StartDrag(DragInfo dragInfo)
{
// DockLeaf не требует дополнительной подготовки
return true;
}
/// <summary>
/// Вызывается при завершении операции перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// Исходная информация о перетаскивании.
/// </param>
/// <param name="effects">
/// Эффекты, которые были применены при сбросе.
/// </param>
/// <remarks>
/// Для <see cref="DockLeaf"/> этот метод не выполняет действий.
/// </remarks>
public void DragCompleted(DragInfo dragInfo, DragDropEffects effects)
{
// Если лист был перемещен или скопирован, LayoutManager уже обработал это
}
/// <summary>
/// Вызывается при отмене операции перетаскивания.
/// </summary>
/// <param name="dragInfo">
/// Исходная информация о перетаскивании.
/// </param>
/// <remarks>
/// Для <see cref="DockLeaf"/> отмена перетаскивания не требует действий.
/// </remarks>
public void DragCancelled(DragInfo dragInfo)
{
// Отмена не требует действий
}
#endregion
#region Реализация IDropTarget
/// <summary>
/// Определяет, может ли лист принять сбрасываемые данные.
/// </summary>
/// <param name="dropInfo">
/// Информация о потенциальном сбросе.
/// </param>
/// <returns>
/// true, если лист может принять данные; в противном случае — false.
/// </returns>
/// <remarks>
/// Лист может принимать:
/// <list type="bullet">
/// <item>
/// <see cref="DockElementDragData"/> для других листов и групп
/// (для объединения или разделения)
/// </item>
/// <item>
/// <see cref="ContentDragData"/> для вкладок (для объединения вкладок)
/// </item>
/// </list>
/// </remarks>
public bool CanAcceptDrop(DropInfo dropInfo)
{
if (dropInfo.Data is DockElementDragData elementData)
{
// Можно принимать другие листы и группы
return elementData.ElementType == nameof(DockLeaf) ||
elementData.ElementType == nameof(DockGroup);
}
else if (dropInfo.Data is ContentDragData contentData)
{
// Можно принимать контент для объединения вкладок
return true;
}
return false;
}
/// <summary>
/// Вызывается, когда перетаскиваемый объект находится над листом.
/// </summary>
/// <param name="dropInfo">
/// Информация о текущем положении перетаскивания.
/// </param>
/// <remarks>
/// <para>
/// В зависимости от типа данных устанавливаются разные предлагаемые эффекты:
/// </para>
/// <list type="bullet">
/// <item>
/// Для <see cref="ContentDragData"/> - эффект копирования (объединение вкладок)
/// </item>
/// <item>
/// Для <see cref="DockElementDragData"/> - эффект перемещения
/// </item>
/// </list>
/// </remarks>
public void DragOver(DropInfo dropInfo)
{
if (CanAcceptDrop(dropInfo))
{
if (dropInfo.Data is ContentDragData)
{
// Для контента предлагаем копирование (объединение вкладок)
dropInfo.SuggestedEffects = DragDropEffects.Copy;
}
else
{
// Для элементов предлагаем перемещение
dropInfo.SuggestedEffects = DragDropEffects.Move;
}
}
else
{
dropInfo.SuggestedEffects = DragDropEffects.None;
}
}
/// <summary>
/// Вызывается, когда пользователь сбрасывает данные на лист.
/// </summary>
/// <param name="dropInfo">
/// Информация о сбросе.
/// </param>
/// <remarks>
/// Обработка сброса делегируется <see cref="LayoutManager"/>.
/// </remarks>
public void Drop(DropInfo dropInfo)
{
// Обработка делегируется LayoutManager
dropInfo.MarkAsHandled();
}
/// <summary>
/// Вызывается, когда перетаскиваемый объект покидает область листа.
/// </summary>
/// <remarks>
/// Очистка визуальной обратной связи выполняется в UI-слое.
/// </remarks>
public void DragLeave()
{
// Очистка визуальной обратной связи
}
#endregion
}
/// <summary>
/// Представляет данные для перетаскивания элементов док-системы (групп или листов).
/// Используется при перетаскивании целых структурных элементов дерева компоновки.
/// </summary>
/// <remarks>
/// Этот класс сериализуется и передается между компонентами системы перетаскивания
/// для идентификации перетаскиваемого элемента и его свойств.
/// </remarks>
public class DockElementDragData
{
/// <summary>
/// Получает или задает уникальный идентификатор элемента.
/// </summary>
/// <value>
/// Идентификатор элемента, соответствующий свойству <see cref="IDockElement.Id"/>.
/// </value>
public string ElementId { get; set; } = string.Empty;
/// <summary>
/// Получает или задает тип элемента.
/// </summary>
/// <value>
/// Имя типа элемента (обычно "DockGroup" или "DockLeaf").
/// </value>
public string ElementType { get; set; } = string.Empty;
/// <summary>
/// Получает или задает значение, указывающее, является ли элемент группой.
/// </summary>
/// <value>
/// true, если элемент является <see cref="DockGroup"/>; false, если <see cref="DockLeaf"/>.
/// </value>
public bool IsGroup { get; set; }
/// <summary>
/// Получает или задает идентификатор родительского элемента.
/// </summary>
/// <value>
/// Идентификатор родительского элемента или null, если элемент корневой.
/// </value>
public string? ParentId { get; set; }
/// <summary>
/// Получает или задает ширину элемента.
/// </summary>
/// <value>
/// Текущая ширина элемента в пикселях.
/// </value>
public double Width { get; set; }
/// <summary>
/// Получает или задает высоту элемента.
/// </summary>
/// <value>
/// Текущая высота элемента в пикселях.
/// </value>
public double Height { get; set; }
}
/// <summary>
/// Представляет данные для перетаскивания контента (вкладок).
/// Используется при перетаскивании отдельных вкладок между контейнерами.
/// </summary>
/// <remarks>
/// Этот класс позволяет идентифицировать конкретную вкладку для операций
/// объединения или перемещения между контейнерами.
/// </remarks>
public class ContentDragData
{
/// <summary>
/// Получает или задает идентификатор контейнера (листа), содержащего контент.
/// </summary>
/// <value>
/// Идентификатор <see cref="DockLeaf"/>, в котором находится перетаскиваемая вкладка.
/// </value>
public string ElementId { get; set; } = string.Empty;
/// <summary>
/// Получает или задает уникальный идентификатор контента.
/// </summary>
/// <value>
/// Идентификатор контента, соответствующий свойству <see cref="IDockContent.Id"/>.
/// </value>
public string ContentId { get; set; } = string.Empty;
/// <summary>
/// Получает или задает заголовок контента.
/// </summary>
/// <value>
/// Текст, отображаемый на вкладке.
/// </value>
public string ContentTitle { get; set; } = string.Empty;
/// <summary>
/// Получает или задает тип контента.
/// </summary>
/// <value>
/// Имя типа контента (например, "TextEditor", "Toolbox", и т.д.).
/// </value>
public string ContentType { get; set; } = string.Empty;
/// <summary>
/// Получает или задает значение, указывающее, можно ли закрыть контент.
/// </summary>
/// <value>
/// true, если контент можно закрыть; в противном случае — false.
/// </value>
public bool CanClose { get; set; } = true;
}

View File

@@ -1,13 +1,33 @@
namespace Lattice.Core.Docking.Models;
/// <summary>
/// Определяет позицию вставки при операции Drag-and-Drop.
/// Определяет позицию вставки элемента относительно целевого элемента.
/// Используется при операциях перемещения и вставки элементов в дерево компоновки.
/// </summary>
public enum DockPosition
{
/// <summary>
/// Слева от целевого элемента.
/// </summary>
Left,
/// <summary>
/// Справа от целевого элемента.
/// </summary>
Right,
/// <summary>
/// Сверху от целевого элемента.
/// </summary>
Top,
/// <summary>
/// Снизу от целевого элемента.
/// </summary>
Bottom,
/// <summary>
/// В центре целевого элемента (для объединения вкладок).
/// </summary>
Center,
}
}

View File

@@ -5,15 +5,23 @@
/// </summary>
public enum DockSide
{
/// <summary> Левая сторона окна. </summary>
/// <summary>
/// Левая сторона окна.
/// </summary>
Left,
/// <summary> Правая сторона окна. </summary>
/// <summary>
/// Правая сторона окна.
/// </summary>
Right,
/// <summary> Верхняя сторона окна. </summary>
/// <summary>
/// Верхняя сторона окна.
/// </summary>
Top,
/// <summary> Нижняя сторона окна. </summary>
/// <summary>
/// Нижняя сторона окна.
/// </summary>
Bottom
}

View File

@@ -3,21 +3,66 @@
namespace Lattice.Core.Docking.Models;
/// <summary>
/// Описывает состояние плавающего окна в системе Lattice.
/// Представляет плавающее окно в системе докинга.
/// Плавающие окна могут перемещаться по экрану независимо от главного окна.
/// </summary>
public class DockWindow
{
/// <summary> Уникальный ID окна для сохранения его позиции в конфиге. </summary>
/// <summary>
/// Получает уникальный идентификатор окна.
/// </summary>
/// <value>
/// Строковый идентификатор, сгенерированный с помощью GUID.
/// Используется для сохранения позиции и размера окна в конфигурации.
/// </value>
public string Id { get; } = Guid.NewGuid().ToString();
/// <summary> Корневой элемент макета внутри данного окна. </summary>
/// <summary>
/// Получает или задает корневой элемент макета внутри данного окна.
/// </summary>
/// <value>
/// Корневой элемент дерева компоновки плавающего окна.
/// </value>
public IDockElement? Root { get; set; }
/// <summary>
/// Получает или задает позицию X окна на экране.
/// </summary>
/// <value>
/// Координата X левого верхнего угла окна в пикселях.
/// </value>
public double X { get; set; }
/// <summary>
/// Получает или задает позицию Y окна на экране.
/// </summary>
/// <value>
/// Координата Y левого верхнего угла окна в пикселях.
/// </value>
public double Y { get; set; }
/// <summary>
/// Получает или задает ширину окна.
/// </summary>
/// <value>
/// Ширина окна в пикселях. Значение по умолчанию: 800.
/// </value>
public double Width { get; set; } = 800;
/// <summary>
/// Получает или задает высоту окна.
/// </summary>
/// <value>
/// Высота окна в пикселях. Значение по умолчанию: 600.
/// </value>
public double Height { get; set; } = 600;
/// <summary> Заголовок окна (обычно берется из активного контента). </summary>
/// <summary>
/// Получает или задает заголовок окна.
/// </summary>
/// <value>
/// Текст заголовка окна. Обычно берется из активного контента.
/// Значение по умолчанию: "Lattice Tool Window".
/// </value>
public string Title { get; set; } = "Lattice Tool Window";
}
}

View File

@@ -1,12 +1,17 @@
namespace Lattice.Core.Docking.Models;
/// <summary>
/// Перечисление направлений разделения пространства внутри группы.
/// Определяет направление разделения пространства внутри группы.
/// </summary>
public enum SplitDirection
{
/// <summary> Разделение по горизонтали (создает левую и правую области). </summary>
/// <summary>
/// Разделение по горизонтали (создает левую и правую области).
/// </summary>
Horizontal,
/// <summary> Разделение по вертикали (создает верхнюю и нижнюю области). </summary>
/// <summary>
/// Разделение по вертикали (создает верхнюю и нижнюю области).
/// </summary>
Vertical
}
}

View File

@@ -5,8 +5,23 @@
/// </summary>
public enum TabPlacement
{
/// <summary>
/// Вкладки располагаются сверху.
/// </summary>
Top,
/// <summary>
/// Вкладки располагаются снизу.
/// </summary>
Bottom,
/// <summary>
/// Вкладки располагаются слева.
/// </summary>
Left,
/// <summary>
/// Вкладки располагаются справа.
/// </summary>
Right,
}