Files
Lattice/Lattice.Core.Docking/Models/DockGroup.cs
2026-01-27 06:07:15 +03:00

254 lines
11 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 System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Lattice.Core.Docking.Models;
/// <summary>
/// Представляет узел дерева компоновки, который разделяет доступную область
/// между двумя дочерними элементами. Этот класс является основным структурным
/// элементом для создания сложных макетов с разделителями.
/// </summary>
/// <remarks>
/// Каждая группа содержит два дочерних элемента (<see cref="First"/> и <see cref="Second"/>),
/// которые могут быть либо другими группами (для создания вложенной структуры),
/// либо листами (<see cref="DockLeaf"/>) с контентом.
/// Направление разделения определяется свойством <see cref="Orientation"/>.
/// </remarks>
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>
/// Строковый идентификатор, уникальный в пределах дерева компоновки.
/// </value>
/// <remarks>
/// Идентификатор используется для сериализации/десериализации макета,
/// поиска элементов и отслеживания изменений в дереве.
/// </remarks>
public string Id
{
get => _id;
internal set
{
if (_id != value)
{
_id = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает родительский элемент в иерархии дерева компоновки.
/// </summary>
/// <value>
/// Родительский элемент или null, если эта группа является корневой.
/// </value>
/// <remarks>
/// Это свойство управляется системой компоновки при добавлении или
/// удалении элементов из дерева.
/// </remarks>
public IDockElement? Parent { get; set; }
/// <summary>
/// Получает или задает первый дочерний элемент (левую или верхнюю область).
/// </summary>
/// <value>
/// Элемент, занимающий первую часть разделенной области.
/// </value>
/// <exception cref="ArgumentNullException">
/// Выбрасывается при попытке установить значение null.
/// </exception>
/// <remarks>
/// При установке нового значения автоматически обновляется свойство
/// <see cref="Parent"/> у дочернего элемента.
/// </remarks>
public IDockElement First
{
get => _first;
set
{
if (_first != value)
{
_first = value ?? throw new ArgumentNullException(nameof(value));
_first.Parent = this;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает второй дочерний элемент (правую или нижнюю область).
/// </summary>
/// <value>
/// Элемент, занимающий вторую часть разделенной области.
/// </value>
/// <exception cref="ArgumentNullException">
/// Выбрасывается при попытке установить значение null.
/// </exception>
/// <remarks>
/// При установке нового значения автоматически обновляется свойство
/// <see cref="Parent"/> у дочернего элемента.
/// </remarks>
public IDockElement Second
{
get => _second;
set
{
if (_second != value)
{
_second = value ?? throw new ArgumentNullException(nameof(value));
_second.Parent = this;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает направление разделения данной группы.
/// </summary>
/// <value>
/// Значение перечисления <see cref="SplitDirection"/>, указывающее,
/// как разделена область: горизонтально или вертикально.
/// </value>
/// <remarks>
/// <list type="bullet">
/// <item><see cref="SplitDirection.Horizontal"/> создает левую и правую области</item>
/// <item><see cref="SplitDirection.Vertical"/> создает верхнюю и нижнюю области</item>
/// </list>
/// </remarks>
public SplitDirection Orientation { get; set; }
/// <summary>
/// Получает или задает соотношение разделения между первым и вторым элементами.
/// </summary>
/// <value>
/// Значение от 0.0 до 1.0, где:
/// <list type="bullet">
/// <item>0.0 - вся область принадлежит второму элементу</item>
/// <item>0.5 - область разделена поровну</item>
/// <item>1.0 - вся область принадлежит первому элементу</item>
/// </list>
/// </value>
/// <remarks>
/// Изменение этого свойства вызывает событие <see cref="PropertyChanged"/>
/// и может привести к перерисовке пользовательского интерфейса.
/// </remarks>
public double SplitRatio
{
get => _splitRatio;
set
{
if (Math.Abs(_splitRatio - value) > double.Epsilon)
{
_splitRatio = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// Получает или задает желаемую ширину элемента.
/// </summary>
/// <value>
/// Ширина в пикселях или относительных единицах.
/// </value>
public double Width { get; set; }
/// <summary>
/// Получает или задает желаемую высоту элемента.
/// </summary>
/// <value>
/// Высота в пикселях или относительных единицах.
/// </value>
public double Height { get; set; }
/// <summary>
/// Получает минимально допустимую ширину элемента.
/// </summary>
/// <value>
/// Минимальная ширина в пикселях, при которой элемент сохраняет функциональность.
/// </value>
/// <remarks>
/// Для группы минимальная ширина вычисляется как сумма минимальных ширин
/// дочерних элементов при горизонтальной ориентации или максимум минимальных
/// ширин при вертикальной ориентации.
/// </remarks>
public double MinWidth => Orientation == SplitDirection.Horizontal
? First.MinWidth + Second.MinWidth
: Math.Max(First.MinWidth, Second.MinWidth);
/// <summary>
/// Получает минимально допустимую высоту элемента.
/// </summary>
/// <value>
/// Минимальная высота в пикселях, при которой элемент сохраняет функциональность.
/// </value>
/// <remarks>
/// Для группы минимальная высота вычисляется как сумма минимальных высот
/// дочерних элементов при вертикальной ориентации или максимум минимальных
/// высот при горизонтальной ориентации.
/// </remarks>
public double MinHeight => Orientation == SplitDirection.Vertical
? First.MinHeight + Second.MinHeight
: Math.Max(First.MinHeight, Second.MinHeight);
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DockGroup"/>.
/// </summary>
/// <param name="first">
/// Первый дочерний элемент (левая или верхняя область).
/// </param>
/// <param name="second">
/// Второй дочерний элемент (правая или нижняя область).
/// </param>
/// <param name="orientation">
/// Направление разделения между дочерними элементами.
/// </param>
/// <param name="id">
/// Уникальный идентификатор группы. Если не указан, генерируется новый GUID.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="first"/> или <paramref name="second"/>
/// равны null.
/// </exception>
/// <remarks>
/// Конструктор автоматически устанавливает свойство <see cref="Parent"/>
/// у дочерних элементов на текущую группу и генерирует уникальный идентификатор,
/// если он не был предоставлен.
/// </remarks>
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));
Orientation = orientation;
Id = id ?? Guid.NewGuid().ToString();
First.Parent = this;
Second.Parent = this;
}
/// <summary>
/// Вызывает событие <see cref="PropertyChanged"/>.
/// </summary>
/// <param name="name">
/// Имя изменившегося свойства. Если не указано, определяется автоматически.
/// </param>
protected void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}