Добавлен проект UI
This commit is contained in:
49
Lattice.UI/Controls/LatticeContextualToolbar.cs
Normal file
49
Lattice.UI/Controls/LatticeContextualToolbar.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.UI.Primitives; // Для доступа к LatticeIcon
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Панель инструментов, автоматически фильтрующая команды на основе текущего контекста Core.
|
||||
/// </summary>
|
||||
public class LatticeContextualToolbar : CommandBar
|
||||
{
|
||||
/// <summary>
|
||||
/// Обновляет список команд на основе предоставленных определений и текущего контекста.
|
||||
/// </summary>
|
||||
/// <param name="actions">Полный список доступных действий.</param>
|
||||
/// <param name="currentContext">Строковый идентификатор активного контекста (например, "CodeEditor").</param>
|
||||
public void UpdateItems(IEnumerable<ActionDefinition> actions, string currentContext)
|
||||
{
|
||||
// Очищаем текущие команды
|
||||
PrimaryCommands.Clear();
|
||||
|
||||
if (actions == null) return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
// Логика 2026: показываем Common (общие), Global или специфичные для контекста команды
|
||||
if (action.TargetContext == "Common" ||
|
||||
action.TargetContext == "Global" ||
|
||||
action.TargetContext == currentContext)
|
||||
{
|
||||
var button = new AppBarButton
|
||||
{
|
||||
Label = action.Label,
|
||||
// Используем наш хелпер LatticeIcon для создания иконки из шрифта Segoe Fluent Icons
|
||||
Icon = LatticeIcon.GetIcon(action.IconKey),
|
||||
IsEnabled = action.IsEnabled
|
||||
};
|
||||
|
||||
// Добавляем всплывающую подсказку (Tooltip)
|
||||
if (!string.IsNullOrEmpty(action.Tooltip))
|
||||
{
|
||||
ToolTipService.SetToolTip(button, action.Tooltip);
|
||||
}
|
||||
|
||||
PrimaryCommands.Add(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Lattice.UI/Controls/LatticeDockHost.cs
Normal file
106
Lattice.UI/Controls/LatticeDockHost.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.UI.Primitives;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Корневой контрол Lattice, отвечающий за отображение и управление макетом докинга.
|
||||
/// </summary>
|
||||
public class LatticeDockHost : Control
|
||||
{
|
||||
public DockAnchorOverlay? AnchorOverlay => GetTemplateChild("AnchorOverlay") as DockAnchorOverlay;
|
||||
|
||||
/// <summary>
|
||||
/// Определяет свойство зависимости для LayoutManager.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ManagerProperty =
|
||||
DependencyProperty.Register(nameof(Manager), typeof(ILayoutService), typeof(LatticeDockHost), new PropertyMetadata(null, OnManagerChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Сервис управления макетом, привязанный к данному хосту.
|
||||
/// </summary>
|
||||
public ILayoutService? Manager
|
||||
{
|
||||
get => (ILayoutService?)GetValue(ManagerProperty);
|
||||
set => SetValue(ManagerProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Указывает конкретный узел, который должен стать корнем для этого хоста.
|
||||
/// Если null — используется Manager.Root.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty RootNodeProperty =
|
||||
DependencyProperty.Register(nameof(RootNode), typeof(LayoutNode), typeof(LatticeDockHost), new PropertyMetadata(null, OnManagerChanged));
|
||||
|
||||
public LayoutNode? RootNode
|
||||
{
|
||||
get => (LayoutNode?)GetValue(RootNodeProperty);
|
||||
set => SetValue(RootNodeProperty, value);
|
||||
}
|
||||
|
||||
public LatticeDockHost()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(LatticeDockHost);
|
||||
}
|
||||
|
||||
private static void OnManagerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is LatticeDockHost host)
|
||||
{
|
||||
// Отписываемся от событий старого менеджера (если он был)
|
||||
if (e.OldValue is ILayoutService oldService)
|
||||
{
|
||||
oldService.LayoutUpdated -= host.OnLayoutUpdated;
|
||||
}
|
||||
|
||||
// Подписываемся на новый менеджер
|
||||
if (e.NewValue is ILayoutService newService)
|
||||
{
|
||||
newService.LayoutUpdated += host.OnLayoutUpdated;
|
||||
host.RebuildUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Именованный метод для обработки обновления макета.
|
||||
/// Позволяет корректно отписываться от событий и избегать утечек памяти.
|
||||
/// </summary>
|
||||
private void OnLayoutUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
// WinUI 3 требует обновления UI только из основного потока
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
this.RebuildUI();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Полностью перестраивает визуальное дерево на основе текущего состояния Core-движка.
|
||||
/// </summary>
|
||||
private void RebuildUI()
|
||||
{
|
||||
if (this.GetTemplateChild("LayoutPresenter") is ContentPresenter presenter)
|
||||
{
|
||||
// Приоритет: сначала проверяем локальный RootNode, затем глобальный Manager.Root
|
||||
var effectiveRoot = RootNode ?? Manager?.Root;
|
||||
|
||||
if (effectiveRoot != null)
|
||||
{
|
||||
var rootPanel = new LayoutPanel(this);
|
||||
rootPanel.Build(effectiveRoot);
|
||||
presenter.Content = rootPanel;
|
||||
}
|
||||
else
|
||||
{
|
||||
presenter.Content = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
Lattice.UI/Controls/LatticeFloatingWindow.cs
Normal file
49
Lattice.UI/Controls/LatticeFloatingWindow.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Обеспечивает поддержку выноса панелей в отдельные нативные окна Windows (Floating Windows).
|
||||
/// </summary>
|
||||
public class LatticeFloatingWindowHost
|
||||
{
|
||||
private readonly ILayoutService _manager;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует хост плавающих окон.
|
||||
/// </summary>
|
||||
/// <param name="manager">Общий менеджер макета приложения.</param>
|
||||
public LatticeFloatingWindowHost(ILayoutService manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает новое окно Windows для конкретного узла макета.
|
||||
/// </summary>
|
||||
/// <param name="node">Узел (панель), который нужно вынести в отдельное окно.</param>
|
||||
public void CreateFromNode(LayoutNode node)
|
||||
{
|
||||
// Создаем новое окно WinUI 3
|
||||
var newWindow = new Window();
|
||||
|
||||
// Создаем и настраиваем хост докинга для нового окна
|
||||
var host = new LatticeDockHost
|
||||
{
|
||||
Manager = _manager, // Передаем общий менеджер, чтобы дерево было синхронизировано
|
||||
RootNode = node, // Указываем хосту отображать ТОЛЬКО этот узел
|
||||
};
|
||||
|
||||
newWindow.Content = host;
|
||||
|
||||
// Настройка нативного окна через AppWindow
|
||||
AppWindow appWin = newWindow.AppWindow;
|
||||
appWin.Title = node.Name;
|
||||
|
||||
// Показываем окно
|
||||
newWindow.Activate();
|
||||
}
|
||||
}
|
||||
83
Lattice.UI/Controls/LatticePane.cs
Normal file
83
Lattice.UI/Controls/LatticePane.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.UI.DragDrop;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Представляет визуальный контейнер для содержимого (панели или документа) в системе Lattice.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = "HeaderPresenter", Type = typeof(FrameworkElement))]
|
||||
[TemplatePart(Name = "ContentPresenter", Type = typeof(ContentPresenter))]
|
||||
[TemplatePart(Name = "PART_CloseButton", Type = typeof(Button))] // Добавлено для ясности
|
||||
public class LatticePane : ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(LatticePane), new PropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty HeaderContentProperty =
|
||||
DependencyProperty.Register(nameof(HeaderContent), typeof(object), typeof(LatticePane), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Событие, возникающее при нажатии на кнопку закрытия в шаблоне.
|
||||
/// </summary>
|
||||
public event RoutedEventHandler? CloseClick;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => (string)GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public object HeaderContent
|
||||
{
|
||||
get => GetValue(HeaderContentProperty);
|
||||
set => SetValue(HeaderContentProperty, value);
|
||||
}
|
||||
|
||||
public LatticePane()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(LatticePane);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
// Логика кнопки закрытия
|
||||
if (GetTemplateChild("PART_CloseButton") is Button closeButton)
|
||||
{
|
||||
closeButton.Click -= OnCloseButtonClick; // Защита от двойной подписки
|
||||
closeButton.Click += OnCloseButtonClick;
|
||||
}
|
||||
|
||||
// Логика перетаскивания (Drag-and-Drop)
|
||||
if (GetTemplateChild("HeaderPresenter") is FrameworkElement header)
|
||||
{
|
||||
this.Loaded += (s, e) =>
|
||||
{
|
||||
var host = FindParentHost(this);
|
||||
if (host != null && this.DataContext is LayoutNode node)
|
||||
{
|
||||
var handler = new DockTabHandler(host);
|
||||
handler.Attach(header, node);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CloseClick?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private LatticeDockHost? FindParentHost(DependencyObject child)
|
||||
{
|
||||
var parent = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(child);
|
||||
if (parent == null) return null;
|
||||
|
||||
if (parent is LatticeDockHost host) return host;
|
||||
return FindParentHost(parent);
|
||||
}
|
||||
}
|
||||
99
Lattice.UI/Controls/LatticeSplitter.cs
Normal file
99
Lattice.UI/Controls/LatticeSplitter.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Разделитель между панелями Lattice, позволяющий динамически изменять их размеры.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = "PART_Thumb", Type = typeof(Thumb))]
|
||||
public class LatticeSplitter : Control
|
||||
{
|
||||
private Thumb? _thumb;
|
||||
|
||||
/// <summary>
|
||||
/// Узел макета, находящийся слева или сверху от разделителя.
|
||||
/// </summary>
|
||||
public LayoutNode? LeftNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Узел макета, находящийся справа или снизу от разделителя.
|
||||
/// </summary>
|
||||
public LayoutNode? RightNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ориентация разделителя, определяющая направление изменения размера.
|
||||
/// </summary>
|
||||
public SplitOrientation Orientation { get; set; }
|
||||
|
||||
public LatticeSplitter()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(LatticeSplitter);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
if (_thumb != null) _thumb.DragDelta -= OnThumbDragDelta;
|
||||
_thumb = GetTemplateChild("PART_Thumb") as Thumb;
|
||||
if (_thumb != null) _thumb.DragDelta += OnThumbDragDelta;
|
||||
}
|
||||
|
||||
private void OnThumbDragDelta(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
if (LeftNode == null || RightNode == null) return;
|
||||
|
||||
// В WinUI 3 (2026) для изменения пропорций Star-размеров
|
||||
// мы корректируем WidthValue/HeightValue и уведомляем менеджер.
|
||||
|
||||
double sensitivity = 0.01; // Коэффициент чувствительности для плавности
|
||||
|
||||
if (Orientation == SplitOrientation.Horizontal)
|
||||
{
|
||||
double delta = e.HorizontalChange * sensitivity;
|
||||
LeftNode.WidthValue += delta;
|
||||
RightNode.WidthValue -= delta;
|
||||
|
||||
// Ограничения минимального размера (10% от доступного пространства)
|
||||
if (LeftNode.WidthValue < 0.1) { RightNode.WidthValue += (LeftNode.WidthValue - 0.1); LeftNode.WidthValue = 0.1; }
|
||||
if (RightNode.WidthValue < 0.1) { LeftNode.WidthValue += (RightNode.WidthValue - 0.1); RightNode.WidthValue = 0.1; }
|
||||
}
|
||||
else // Vertical
|
||||
{
|
||||
double delta = e.VerticalChange * sensitivity;
|
||||
LeftNode.HeightValue += delta;
|
||||
RightNode.HeightValue -= delta;
|
||||
|
||||
if (LeftNode.HeightValue < 0.1) { RightNode.HeightValue += (LeftNode.HeightValue - 0.1); LeftNode.HeightValue = 0.1; }
|
||||
if (RightNode.HeightValue < 0.1) { LeftNode.HeightValue += (RightNode.HeightValue - 0.1); RightNode.HeightValue = 0.1; }
|
||||
}
|
||||
|
||||
// Уведомляем систему об изменении макета через родительский хост
|
||||
NotifyLayoutUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Находит хост и вызывает обновление визуального дерева.
|
||||
/// </summary>
|
||||
private void NotifyLayoutUpdated()
|
||||
{
|
||||
DependencyObject parent = VisualTreeHelper.GetParent(this);
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent is LatticeDockHost host)
|
||||
{
|
||||
// Вызываем метод перерисовки (в Core это может быть событие LayoutUpdated)
|
||||
// В нашем случае это заставит LayoutPanel пересчитать Column/Row Definitions
|
||||
host.Manager?.Dock(null!, null!, DockDirection.Center); // Фиктивный вызов для обновления
|
||||
// Или если есть прямой доступ: host.Manager.InvokeLayoutUpdated();
|
||||
break;
|
||||
}
|
||||
parent = VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Lattice.UI/Controls/LatticeTabStrip.cs
Normal file
43
Lattice.UI/Controls/LatticeTabStrip.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Расширенный TabView для центральной области Lattice.
|
||||
/// </summary>
|
||||
public class LatticeTabStrip : TabView
|
||||
{
|
||||
private IContextService? _contextService;
|
||||
|
||||
public LatticeTabStrip()
|
||||
{
|
||||
this.TabCloseRequested += (s, e) =>
|
||||
{
|
||||
// Логика удаления вкладки из коллекции
|
||||
this.TabItems.Remove(e.Tab);
|
||||
|
||||
// Если вкладок не осталось, сбрасываем контекст
|
||||
if (this.TabItems.Count == 0)
|
||||
{
|
||||
_contextService?.SetContext("Common");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void Initialize(IContextService contextService)
|
||||
{
|
||||
_contextService = contextService;
|
||||
this.SelectionChanged += OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (this.SelectedItem is IDockableComponent component)
|
||||
{
|
||||
// Уведомляем ядро о смене контекста для обновления кнопок
|
||||
_contextService?.SetContext(component.ContextGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user