DragAndDrop core
This commit is contained in:
30
Lattice.Layout.UI.WinUI/Controls/WinUIGroupControl.cs
Normal file
30
Lattice.Layout.UI.WinUI/Controls/WinUIGroupControl.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
134
Lattice.Layout.UI.WinUI/Controls/WinUIItemControl.cs
Normal file
134
Lattice.Layout.UI.WinUI/Controls/WinUIItemControl.cs
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
134
Lattice.Layout.UI.WinUI/Controls/WinUILayoutHost.cs
Normal file
134
Lattice.Layout.UI.WinUI/Controls/WinUILayoutHost.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
85
Lattice.Layout.UI.WinUI/Controls/WinUISplitControl.cs
Normal file
85
Lattice.Layout.UI.WinUI/Controls/WinUISplitControl.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
83
Lattice.Layout.UI.WinUI/Docking/DockOverlay.cs
Normal file
83
Lattice.Layout.UI.WinUI/Docking/DockOverlay.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||
|
||||
/// <summary>
|
||||
/// Полупрозрачная подсветка зоны докинга.
|
||||
/// </summary>
|
||||
public sealed class DockOverlay : Control
|
||||
{
|
||||
public static readonly DependencyProperty ZoneProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Zone),
|
||||
typeof(DockZone),
|
||||
typeof(DockOverlay),
|
||||
new PropertyMetadata(DockZone.Center, OnVisualPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty BoundsProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Bounds),
|
||||
typeof(Rect),
|
||||
typeof(DockOverlay),
|
||||
new PropertyMetadata(Rect.Empty, OnVisualPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Зона докинга, которую нужно подсветить.
|
||||
/// </summary>
|
||||
public DockZone Zone
|
||||
{
|
||||
get => (DockZone)GetValue(ZoneProperty);
|
||||
set => SetValue(ZoneProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Прямоугольник зоны в координатах родительского контейнера.
|
||||
/// </summary>
|
||||
public Rect Bounds
|
||||
{
|
||||
get => (Rect)GetValue(BoundsProperty);
|
||||
set => SetValue(BoundsProperty, value);
|
||||
}
|
||||
|
||||
private Border? _border;
|
||||
|
||||
public DockOverlay()
|
||||
{
|
||||
IsHitTestVisible = false;
|
||||
DefaultStyleKey = typeof(DockOverlay);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
_border = GetTemplateChild("PART_Border") as Border;
|
||||
UpdateVisual();
|
||||
}
|
||||
|
||||
private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockOverlay overlay)
|
||||
{
|
||||
overlay.UpdateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisual()
|
||||
{
|
||||
if (_border is null)
|
||||
return;
|
||||
|
||||
Canvas.SetLeft(this, Bounds.X);
|
||||
Canvas.SetTop(this, Bounds.Y);
|
||||
Width = Bounds.Width;
|
||||
Height = Bounds.Height;
|
||||
|
||||
_border.BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.DeepSkyBlue);
|
||||
_border.BorderThickness = new Thickness(2);
|
||||
_border.Background = new SolidColorBrush(Microsoft.UI.Colors.LightSkyBlue) { Opacity = 0.25 };
|
||||
}
|
||||
}
|
||||
47
Lattice.Layout.UI.WinUI/Docking/DockOverlayHost.cs
Normal file
47
Lattice.Layout.UI.WinUI/Docking/DockOverlayHost.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Lattice.Layout.UI.Docking;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер для отображения подсветки зон докинга.
|
||||
/// Обычно используется как Overlay-слой внутри WinUILayoutHost.
|
||||
/// </summary>
|
||||
public sealed class DockOverlayHost : Canvas
|
||||
{
|
||||
private DockOverlay? _currentOverlay;
|
||||
|
||||
public DockOverlayHost()
|
||||
{
|
||||
IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает подсветку для указанной цели докинга.
|
||||
/// </summary>
|
||||
public void ShowOverlay(DockTarget target, Rect bounds)
|
||||
{
|
||||
if (_currentOverlay is null)
|
||||
{
|
||||
_currentOverlay = new DockOverlay();
|
||||
Children.Add(_currentOverlay);
|
||||
}
|
||||
|
||||
_currentOverlay.Zone = target.Zone;
|
||||
_currentOverlay.Bounds = bounds;
|
||||
_currentOverlay.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Скрывает подсветку зоны докинга.
|
||||
/// </summary>
|
||||
public void HideOverlay()
|
||||
{
|
||||
if (_currentOverlay is not null)
|
||||
{
|
||||
_currentOverlay.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Lattice.Layout.UI.WinUI/Docking/DockZoneHitTester.cs
Normal file
78
Lattice.Layout.UI.WinUI/Docking/DockZoneHitTester.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Lattice.Layout.UI.Docking;
|
||||
using Lattice.Layout.UI.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет hit-test для определения зоны докинга в WinUI.
|
||||
/// </summary>
|
||||
public static class DockZoneHitTester
|
||||
{
|
||||
/// <summary>
|
||||
/// Выполняет hit-test по экранной точке и возвращает цель докинга.
|
||||
/// </summary>
|
||||
/// <param name="host">WinUI-хост раскладки.</param>
|
||||
/// <param name="screenPoint">Точка в координатах окна.</param>
|
||||
public static DockTarget? HitTest(WinUILayoutHost host, Point screenPoint)
|
||||
{
|
||||
if (host is null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
|
||||
// Предполагаем, что LayoutLayer — основной слой, в котором живёт визуальное дерево.
|
||||
var layoutLayer = host.LayoutLayer;
|
||||
if (layoutLayer is null)
|
||||
return null;
|
||||
|
||||
// Переводим координаты в систему координат layoutLayer.
|
||||
var elements = VisualTreeHelper.FindElementsInHostCoordinates(screenPoint, host.LayoutLayer);
|
||||
|
||||
var firstElement = elements.FirstOrDefault();
|
||||
|
||||
if (firstElement is null)
|
||||
return null;
|
||||
|
||||
// Ищем ближайший IWinUIVisual.
|
||||
var visual = FindVisual(firstElement);
|
||||
if (visual is null)
|
||||
return null;
|
||||
|
||||
if (visual is not ILayoutVisual layoutVisual)
|
||||
return null;
|
||||
|
||||
// Вычисляем зону докинга для найденного контрола.
|
||||
var control = visual.Control;
|
||||
|
||||
var bounds = control.TransformToVisual(layoutLayer)
|
||||
.TransformBounds(new Rect(0, 0, control.ActualWidth, control.ActualHeight));
|
||||
|
||||
var localX = screenPoint.X - bounds.X;
|
||||
var localY = screenPoint.Y - bounds.Y;
|
||||
|
||||
var zone = DockingUtils.GetZone(localX, localY, bounds.Width, bounds.Height);
|
||||
|
||||
return new DockTarget(layoutVisual, zone);
|
||||
}
|
||||
|
||||
private static IWinUIVisual? FindVisual(UIElement element)
|
||||
{
|
||||
DependencyObject? current = element;
|
||||
|
||||
while (current is not null)
|
||||
{
|
||||
if (current is FrameworkElement fe && fe.DataContext is IWinUIVisual ctxVisual)
|
||||
return ctxVisual;
|
||||
|
||||
if (current is IWinUIVisual winuiVisual)
|
||||
return winuiVisual;
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
15
Lattice.Layout.UI.WinUI/Docking/IWinUIVisual.cs
Normal file
15
Lattice.Layout.UI.WinUI/Docking/IWinUIVisual.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для визуальных элементов WinUI, соответствующих элементам раскладки.
|
||||
/// Нужен для hit-test и расчёта зон докинга.
|
||||
/// </summary>
|
||||
public interface IWinUIVisual
|
||||
{
|
||||
/// <summary>
|
||||
/// Реальный WinUI-элемент, отображающий данный визуальный элемент раскладки.
|
||||
/// </summary>
|
||||
FrameworkElement Control { get; }
|
||||
}
|
||||
118
Lattice.Layout.UI.WinUI/Helpers/LayoutHostExtensions.cs
Normal file
118
Lattice.Layout.UI.WinUI/Helpers/LayoutHostExtensions.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Lattice.Layout.UI.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Набор вспомогательных методов для упрощённой работы с визуальным хостом раскладки.
|
||||
/// Позволяет быстро подключать LayoutManager, обновлять UI и связывать содержимое.
|
||||
/// </summary>
|
||||
public static class LayoutHostExtensions
|
||||
{
|
||||
// ------------------------------------------------------------------------
|
||||
// 1. Подключение LayoutManager к любому ILayoutView
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Подписывает визуальный хост на события изменения раскладки.
|
||||
/// При каждом изменении модели вызывается <see cref="ILayoutView.Refresh"/>.
|
||||
/// </summary>
|
||||
/// <param name="view">Визуальный хост раскладки.</param>
|
||||
/// <param name="manager">Менеджер раскладки.</param>
|
||||
public static void BindToManager(this ILayoutView view, ILayoutManager manager)
|
||||
{
|
||||
manager.LayoutChanged += (_, _) => view.Refresh();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 2. Полная инициализация раскладки (Root + Manager)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает корневой элемент раскладки, подключает менеджер и выполняет начальную отрисовку.
|
||||
/// </summary>
|
||||
/// <param name="view">Визуальный хост раскладки.</param>
|
||||
/// <param name="root">Корневой элемент раскладки.</param>
|
||||
/// <param name="manager">Менеджер раскладки.</param>
|
||||
public static void UseLayout(this ILayoutView view, ILayoutRoot root, ILayoutManager manager)
|
||||
{
|
||||
view.Root = root;
|
||||
view.BindToManager(manager);
|
||||
view.Refresh();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 3. Удобный fluent-API
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает корневой элемент раскладки и возвращает хост для fluent-цепочек.
|
||||
/// </summary>
|
||||
public static T WithRoot<T>(this T view, ILayoutRoot root)
|
||||
where T : ILayoutView
|
||||
{
|
||||
view.Root = root;
|
||||
return view;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подключает менеджер раскладки и возвращает хост для fluent-цепочек.
|
||||
/// </summary>
|
||||
public static T WithManager<T>(this T view, ILayoutManager manager)
|
||||
where T : ILayoutView
|
||||
{
|
||||
view.BindToManager(manager);
|
||||
return view;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет начальную отрисовку и возвращает хост для fluent-цепочек.
|
||||
/// </summary>
|
||||
public static T Initialize<T>(this T view)
|
||||
where T : ILayoutView
|
||||
{
|
||||
view.Refresh();
|
||||
return view;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 4. Поддержка WinUILayoutHost: резолвер контента
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает функцию, которая по ContentId возвращает реальный UI-элемент.
|
||||
/// Используется для отображения содержимого вкладок.
|
||||
/// </summary>
|
||||
/// <param name="host">WinUI-хост раскладки.</param>
|
||||
/// <param name="resolver">Функция, возвращающая UIElement по ContentId.</param>
|
||||
public static void UseContentResolver(this WinUILayoutHost host, Func<string, UIElement> resolver)
|
||||
{
|
||||
host.ContentResolver = resolver;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 5. Полная WinUI-инициализация (Root + Manager + ContentResolver)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Полностью инициализирует WinUI-хост раскладки:
|
||||
/// устанавливает корень, подключает менеджер, задаёт резолвер содержимого и выполняет отрисовку.
|
||||
/// </summary>
|
||||
/// <param name="host">WinUI-хост раскладки.</param>
|
||||
/// <param name="root">Корневой элемент раскладки.</param>
|
||||
/// <param name="manager">Менеджер раскладки.</param>
|
||||
/// <param name="resolver">Функция получения UI-содержимого по ContentId.</param>
|
||||
public static void UseLayout(
|
||||
this WinUILayoutHost host,
|
||||
ILayoutRoot root,
|
||||
ILayoutManager manager,
|
||||
Func<string, UIElement> resolver)
|
||||
{
|
||||
host.Root = root;
|
||||
host.BindToManager(manager);
|
||||
host.ContentResolver = resolver;
|
||||
host.Refresh();
|
||||
}
|
||||
}
|
||||
18
Lattice.Layout.UI.WinUI/Lattice.Layout.UI.WinUI.csproj
Normal file
18
Lattice.Layout.UI.WinUI/Lattice.Layout.UI.WinUI.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Lattice.Layout.UI.WinUI</RootNamespace>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WinUISDKReferences>false</WinUISDKReferences>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lattice.Layout.UI\Lattice.Layout.UI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
30
Lattice.Layout.UI.WinUI/Rendering/WinUIVisualFactory.cs
Normal file
30
Lattice.Layout.UI.WinUI/Rendering/WinUIVisualFactory.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Lattice.Layout.UI.WinUI.Visuals;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Фабрика визуальных элементов для WinUI.
|
||||
/// Создаёт визуальные представления сплитов, групп и элементов.
|
||||
/// </summary>
|
||||
public sealed class WinUIVisualFactory : ILayoutVisualFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ILayoutVisual CreateSplit(ILayoutSplit split, IReadOnlyList<ILayoutVisual> children)
|
||||
{
|
||||
return new WinUISplitVisual(split, children);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayoutVisual CreateGroup(ILayoutGroup group, IReadOnlyList<ILayoutVisual> items)
|
||||
{
|
||||
return new WinUIGroupVisual(group, items);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILayoutVisual CreateItem(ILayoutItem item)
|
||||
{
|
||||
return new WinUIItemVisual(item);
|
||||
}
|
||||
}
|
||||
81
Lattice.Layout.UI.WinUI/Visuals/WinUIGroupVisual.cs
Normal file
81
Lattice.Layout.UI.WinUI/Visuals/WinUIGroupVisual.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Lattice.Layout.UI.WinUI.Controls;
|
||||
using Lattice.Layout.UI.WinUI.Docking;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальное представление группы вкладок для WinUI.
|
||||
/// Управляет <see cref="WinUIGroupControl"/> и вкладками <see cref="TabViewItem"/>.
|
||||
/// </summary>
|
||||
public sealed class WinUIGroupVisual : LayoutVisual, IWinUIVisual
|
||||
{
|
||||
/// <summary>
|
||||
/// Реальный WinUI-контрол группы вкладок.
|
||||
/// </summary>
|
||||
public WinUIGroupControl GroupControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Контрол, используемый докингом и рендерером.
|
||||
/// </summary>
|
||||
public FrameworkElement Control => GroupControl;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальные элементы вкладок.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ILayoutVisual> Items { get; }
|
||||
|
||||
public WinUIGroupVisual(ILayoutGroup model, IReadOnlyList<ILayoutVisual> items)
|
||||
: base(model)
|
||||
{
|
||||
Items = items;
|
||||
GroupControl = new WinUIGroupControl();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
// Полная очистка
|
||||
GroupControl.TabView.TabItems.Clear();
|
||||
|
||||
foreach (var visual in Items)
|
||||
{
|
||||
visual.Attach();
|
||||
|
||||
if (visual is WinUIItemVisual itemVisual)
|
||||
{
|
||||
var tab = new TabViewItem
|
||||
{
|
||||
Header = itemVisual.Header,
|
||||
Content = itemVisual.Control // безопасно, т.к. Control — UserControl
|
||||
};
|
||||
|
||||
GroupControl.TabView.TabItems.Add(tab);
|
||||
}
|
||||
}
|
||||
|
||||
// Активная вкладка по модели
|
||||
if (Model is ILayoutGroup group && group.ActiveItem is not null)
|
||||
{
|
||||
var index = group.Items is IList<ILayoutItem> list
|
||||
? list.IndexOf(group.ActiveItem)
|
||||
: group.Items.ToList().IndexOf(group.ActiveItem);
|
||||
|
||||
if (index >= 0 && index < GroupControl.TabView.TabItems.Count)
|
||||
GroupControl.TabView.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
foreach (var visual in Items)
|
||||
visual.Detach();
|
||||
|
||||
GroupControl.TabView.TabItems.Clear();
|
||||
}
|
||||
}
|
||||
51
Lattice.Layout.UI.WinUI/Visuals/WinUIItemVisual.cs
Normal file
51
Lattice.Layout.UI.WinUI/Visuals/WinUIItemVisual.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Lattice.Layout.UI.WinUI.Controls;
|
||||
using Lattice.Layout.UI.WinUI.Docking;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальное представление конечного элемента раскладки для WinUI.
|
||||
/// Оборачивает содержимое в <see cref="WinUIItemControl"/>.
|
||||
/// </summary>
|
||||
public sealed class WinUIItemVisual : LayoutVisual, IWinUIVisual
|
||||
{
|
||||
/// <summary>
|
||||
/// Реальный WinUI-контрол, отображающий элемент.
|
||||
/// </summary>
|
||||
public WinUIItemControl ItemControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Контрол, используемый докингом и рендерером.
|
||||
/// </summary>
|
||||
public FrameworkElement Control => ItemControl;
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок вкладки или элемента.
|
||||
/// </summary>
|
||||
public string Header => ((ILayoutItem)Model).Title;
|
||||
|
||||
public WinUIItemVisual(ILayoutItem model)
|
||||
: base(model)
|
||||
{
|
||||
ItemControl = new WinUIItemControl
|
||||
{
|
||||
ContentId = model.ContentId
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
// Здесь можно привязать реальное содержимое по ContentId через Shell/Service.
|
||||
// Например:
|
||||
// ItemControl.Content = resolver(((ILayoutItem)Model).ContentId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
ItemControl.Content = null;
|
||||
}
|
||||
}
|
||||
82
Lattice.Layout.UI.WinUI/Visuals/WinUISplitVisual.cs
Normal file
82
Lattice.Layout.UI.WinUI/Visuals/WinUISplitVisual.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Lattice.Layout.Abstractions;
|
||||
using Lattice.Layout.UI.WinUI.Controls;
|
||||
using Lattice.Layout.UI.WinUI.Docking;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальное представление сплит-элемента для WinUI.
|
||||
/// Управляет жизненным циклом соответствующего <see cref="WinUISplitControl"/>.
|
||||
/// </summary>
|
||||
public sealed class WinUISplitVisual : LayoutVisual, IWinUIVisual
|
||||
{
|
||||
/// <summary>
|
||||
/// Реальный WinUI-контрол, отображающий сплит.
|
||||
/// </summary>
|
||||
public WinUISplitControl SplitControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Контрол, используемый рендерером и докингом.
|
||||
/// </summary>
|
||||
public FrameworkElement Control => SplitControl;
|
||||
|
||||
/// <summary>
|
||||
/// Дочерние визуальные элементы.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ILayoutVisual> Children { get; }
|
||||
|
||||
public WinUISplitVisual(ILayoutSplit model, IReadOnlyList<ILayoutVisual> children)
|
||||
: base(model)
|
||||
{
|
||||
Children = children;
|
||||
|
||||
SplitControl = new WinUISplitControl
|
||||
{
|
||||
LayoutOrientation = model.Orientation switch
|
||||
{
|
||||
Lattice.Layout.Abstractions.Orientation.Horizontal => Microsoft.UI.Xaml.Controls.Orientation.Horizontal,
|
||||
Lattice.Layout.Abstractions.Orientation.Vertical => Microsoft.UI.Xaml.Controls.Orientation.Vertical,
|
||||
_ => Microsoft.UI.Xaml.Controls.Orientation.Horizontal
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
SplitControl.Children.Clear();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Attach();
|
||||
|
||||
switch (child)
|
||||
{
|
||||
case WinUISplitVisual splitVisual:
|
||||
SplitControl.Children.Add(splitVisual.Control);
|
||||
break;
|
||||
|
||||
case WinUIGroupVisual groupVisual:
|
||||
SplitControl.Children.Add(groupVisual.Control);
|
||||
break;
|
||||
|
||||
case WinUIItemVisual itemVisual:
|
||||
SplitControl.Children.Add(itemVisual.Control);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SplitControl.RebuildGrid();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
foreach (var child in Children)
|
||||
child.Detach();
|
||||
|
||||
SplitControl.Children.Clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user