DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Lattice.Layout.UI.WinUI.Controls;
/// <summary>
/// Контрол для отображения группы вкладок.
/// Содержит заголовки вкладок и область содержимого.
/// </summary>
public sealed class WinUIGroupControl : Grid
{
/// <summary>
/// Контрол TabView, содержащий вкладки и их содержимое.
/// </summary>
public TabView TabView { get; }
/// <summary>
/// Создаёт новый экземпляр <see cref="WinUIGroupControl"/>.
/// </summary>
public WinUIGroupControl()
{
TabView = new TabView
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
Children.Add(TabView);
}
}

View File

@@ -0,0 +1,134 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
namespace Lattice.Layout.UI.WinUI.Controls;
/// <summary>
/// Контрол для отображения содержимого конечного элемента раскладки.
/// Является контейнером для реального UI-контента, подставляемого через ContentResolver.
/// </summary>
public sealed class WinUIItemControl : ContentControl
{
/// <summary>
/// Идентификатор содержимого, связанного с данным элементом.
/// Используется для подстановки реального UI-контрола через ContentResolver.
/// </summary>
public string? ContentId
{
get => (string?)GetValue(ContentIdProperty);
set => SetValue(ContentIdProperty, value);
}
public static readonly DependencyProperty ContentIdProperty =
DependencyProperty.Register(
nameof(ContentId),
typeof(string),
typeof(WinUIItemControl),
new PropertyMetadata(default(string), OnContentIdChanged));
/// <summary>
/// Делегат, который должен вернуть реальный UI-контент по ContentId.
/// Устанавливается WinUILayoutHost.
/// </summary>
public Func<string, UIElement?>? ContentResolver
{
get => (Func<string, UIElement?>?)GetValue(ContentResolverProperty);
set => SetValue(ContentResolverProperty, value);
}
public static readonly DependencyProperty ContentResolverProperty =
DependencyProperty.Register(
nameof(ContentResolver),
typeof(Func<string, UIElement?>),
typeof(WinUIItemControl),
new PropertyMetadata(null, OnContentResolverChanged));
/// <summary>
/// Вызывается, когда контент успешно загружен.
/// </summary>
public event Action<WinUIItemControl>? ContentLoaded;
/// <summary>
/// Вызывается, когда контент был очищен (Detach).
/// </summary>
public event Action<WinUIItemControl>? ContentCleared;
/// <summary>
/// Создаёт новый экземпляр <see cref="WinUIItemControl"/>.
/// </summary>
public WinUIItemControl()
{
HorizontalContentAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;
// Fallback-контент, если ContentResolver не установлен
Content = CreatePlaceholder();
}
private static void OnContentIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinUIItemControl control)
control.TryLoadContent();
}
private static void OnContentResolverChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinUIItemControl control)
control.TryLoadContent();
}
/// <summary>
/// Пытается загрузить реальный контент по ContentId.
/// </summary>
private void TryLoadContent()
{
if (ContentId is null || ContentResolver is null)
{
Content = CreatePlaceholder();
return;
}
var resolved = ContentResolver(ContentId);
if (resolved is null)
{
Content = CreatePlaceholder($"Контент '{ContentId}' не найден");
return;
}
Content = resolved;
ContentLoaded?.Invoke(this);
}
/// <summary>
/// Очищает контент (используется визуалом при Detach).
/// </summary>
public void ClearContent()
{
Content = CreatePlaceholder();
ContentCleared?.Invoke(this);
}
/// <summary>
/// Создаёт placeholder-контент, отображаемый до загрузки реального UI.
/// </summary>
private static UIElement CreatePlaceholder(string? message = null)
{
return new Border
{
Background = new SolidColorBrush(Microsoft.UI.Colors.Transparent),
BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.Gray),
BorderThickness = new Thickness(1),
Padding = new Thickness(8),
Child = new TextBlock
{
Text = message ?? "Нет содержимого",
Opacity = 0.6,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
}
}

View File

@@ -0,0 +1,134 @@
using Lattice.Layout.Abstractions;
using Lattice.Layout.UI.Docking;
using Lattice.Layout.UI.WinUI.Docking;
using Lattice.Layout.UI.WinUI.Rendering;
using Lattice.Layout.UI.WinUI.Visuals;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace Lattice.Layout.UI.WinUI.Controls;
/// <summary>
/// WinUI-контрол, отображающий дерево раскладки.
/// Оборачивает LayoutRenderer и размещает визуальное дерево внутри себя.
/// </summary>
public sealed class WinUILayoutHost : UserControl, ILayoutView
{
/// <summary>
/// Слой, в котором размещается визуальное дерево раскладки.
/// </summary>
public Grid LayoutLayer { get; }
/// <summary>
/// Слой для отображения подсветки зон докинга.
/// </summary>
public DockOverlayHost OverlayLayer { get; }
private readonly LayoutRenderer _renderer;
private readonly WinUIVisualFactory _factory;
/// <summary>
/// Функция, возвращающая UI-содержимое по ContentId.
/// Используется визуальными элементами для подстановки реального контрола.
/// </summary>
public Func<string, UIElement>? ContentResolver { get; set; }
/// <summary>
/// Корневой элемент раскладки, который необходимо отобразить.
/// </summary>
public ILayoutRoot? Root
{
get => (ILayoutRoot?)GetValue(RootProperty);
set => SetValue(RootProperty, value);
}
/// <summary>
/// Свойство зависимости для <see cref="Root"/>.
/// </summary>
public static readonly DependencyProperty RootProperty =
DependencyProperty.Register(
nameof(Root),
typeof(ILayoutRoot),
typeof(WinUILayoutHost),
new PropertyMetadata(null, OnRootChanged));
private static void OnRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinUILayoutHost host)
{
host.Refresh();
}
}
/// <summary>
/// Создаёт новый экземпляр <see cref="WinUILayoutHost"/>.
/// </summary>
public WinUILayoutHost()
{
LayoutLayer = new Grid();
OverlayLayer = new DockOverlayHost();
var rootGrid = new Grid();
rootGrid.Children.Add(LayoutLayer);
rootGrid.Children.Add(OverlayLayer);
Content = rootGrid;
_factory = new WinUIVisualFactory();
_renderer = new LayoutRenderer(_factory);
}
/// <summary>
/// Выполняет полную перерисовку визуального дерева.
/// </summary>
public void Refresh()
{
LayoutLayer.Children.Clear();
if (Root?.Child is null)
return;
var visual = _renderer.Build(Root.Child);
visual.Attach();
switch (visual)
{
case WinUISplitVisual splitVisual:
LayoutLayer.Children.Add(splitVisual.Control);
break;
case WinUIGroupVisual groupVisual:
LayoutLayer.Children.Add(groupVisual.Control);
break;
case WinUIItemVisual itemVisual:
LayoutLayer.Children.Add(itemVisual.Control);
break;
}
}
/// <summary>
/// Показывает подсветку зоны докинга для указанной цели.
/// </summary>
public void ShowDockOverlay(DockTarget target)
{
if (target.Visual is not IWinUIVisual winuiVisual)
return;
var control = winuiVisual.Control;
var bounds = control.TransformToVisual(LayoutLayer)
.TransformBounds(new Windows.Foundation.Rect(0, 0, control.ActualWidth, control.ActualHeight));
OverlayLayer.ShowOverlay(target, bounds);
}
/// <summary>
/// Скрывает подсветку зон докинга.
/// </summary>
public void HideDockOverlay()
{
OverlayLayer.HideOverlay();
}
}

View File

@@ -0,0 +1,85 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Lattice.Layout.UI.WinUI.Controls;
/// <summary>
/// Контейнер для отображения сплит-элемента раскладки.
/// Использует Grid и автоматически создаёт строки/столбцы под детей.
/// </summary>
public sealed class WinUISplitControl : Grid
{
/// <summary>
/// Ориентация сплита (горизонтальная или вертикальная).
/// </summary>
public Orientation LayoutOrientation
{
get => (Orientation)GetValue(LayoutOrientationProperty);
set => SetValue(LayoutOrientationProperty, value);
}
public static readonly DependencyProperty LayoutOrientationProperty =
DependencyProperty.Register(
nameof(LayoutOrientation),
typeof(Orientation),
typeof(WinUISplitControl),
new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinUISplitControl control)
control.RebuildGrid();
}
public WinUISplitControl()
{
Loaded += (_, _) => RebuildGrid();
}
/// <summary>
/// Перестраивает структуру Grid в зависимости от ориентации и количества детей.
/// </summary>
public void RebuildGrid()
{
RowDefinitions.Clear();
ColumnDefinitions.Clear();
if (Children.Count == 0)
return;
if (LayoutOrientation == Orientation.Horizontal)
{
// Горизонтальный сплит → столбцы
for (int i = 0; i < Children.Count; i++)
{
ColumnDefinitions.Add(new ColumnDefinition
{
Width = new GridLength(1, GridUnitType.Star)
});
if (Children[i] is FrameworkElement fe) Grid.SetColumn(fe, i);
}
}
else
{
// Вертикальный сплит → строки
for (int i = 0; i < Children.Count; i++)
{
RowDefinitions.Add(new RowDefinition
{
Height = new GridLength(1, GridUnitType.Star)
});
if (Children[i] is FrameworkElement fe) Grid.SetColumn(fe, i);
}
}
}
/// <summary>
/// Добавляет дочерний элемент и перестраивает Grid.
/// </summary>
public new void ChildrenChanged()
{
RebuildGrid();
}
}