Compare commits
2 Commits
b6de0543b7
...
c3770c789b
| Author | SHA1 | Date | |
|---|---|---|---|
| c3770c789b | |||
| ca5d912c9c |
17
Lattice.Core/Abstractions/INotificationService.cs
Normal file
17
Lattice.Core/Abstractions/INotificationService.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
|
||||
namespace Lattice.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Описывает сервис для рассылки уведомлений внутри системы Lattice.
|
||||
/// </summary>
|
||||
public interface INotificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Событие, возникающее при отправке нового сообщения.
|
||||
/// </summary>
|
||||
event EventHandler<NotificationEventArgs> NotificationReceived;
|
||||
|
||||
void Show(string message, NotificationSeverity severity = NotificationSeverity.Info, int durationSeconds = 5);
|
||||
}
|
||||
@@ -1,28 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Поддержка LTS версий и актуальной на 2026 год .NET 10 -->
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Lattice.Core</AssemblyName>
|
||||
<RootNamespace>Lattice.Core</RootNamespace>
|
||||
|
||||
<!-- Метаданные разработчика -->
|
||||
<Authors>FrigaT</Authors>
|
||||
<Company>FrigaT</Company>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||
<Description>Core docking and layout engine for Lattice UI (WinUI 3 / Uno Platform).</Description>
|
||||
|
||||
<!-- Совместимость с Uno Platform (Trimming и AOT) -->
|
||||
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsTrimmable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,12 +11,17 @@ public record ActionDefinition
|
||||
public string Id { get; init; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Текст кнопки.
|
||||
/// Текст кнопки, отображаемый пользователю.
|
||||
/// </summary>
|
||||
public string Label { get; init; } = "Action";
|
||||
|
||||
/// <summary>
|
||||
/// Группа контекста, к которой привязана кнопка (например, "CodeEditor").
|
||||
/// Код иконки из шрифта Segoe Fluent Icons (например, "\uE102").
|
||||
/// </summary>
|
||||
public string IconKey { get; init; } = "\uE102";
|
||||
|
||||
/// <summary>
|
||||
/// Группа контекста, к которой привязана кнопка (например, "CodeEditor", "Common").
|
||||
/// </summary>
|
||||
public string TargetContext { get; init; } = "Common";
|
||||
|
||||
@@ -26,7 +31,7 @@ public record ActionDefinition
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Подсказка (Tooltip).
|
||||
/// Подсказка, отображаемая при наведении (Tooltip).
|
||||
/// </summary>
|
||||
public string Tooltip { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@ public enum DockDirection
|
||||
Top,
|
||||
Bottom,
|
||||
Floating,
|
||||
}
|
||||
}
|
||||
|
||||
8
Lattice.Core/Models/Enums/NotificationSeverity.cs
Normal file
8
Lattice.Core/Models/Enums/NotificationSeverity.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Lattice.Core.Models.Enums;
|
||||
|
||||
public enum NotificationSeverity {
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
5
Lattice.Core/Models/NotificationEventArgs.cs
Normal file
5
Lattice.Core/Models/NotificationEventArgs.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Lattice.Core.Models.Enums;
|
||||
|
||||
namespace Lattice.Core.Models;
|
||||
|
||||
public record NotificationEventArgs(string Message, NotificationSeverity Severity, int DurationSeconds);
|
||||
@@ -1,11 +1,11 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
|
||||
namespace Lattice.Core.Context;
|
||||
namespace Lattice.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления контекстом приложения.
|
||||
/// </summary>
|
||||
public class ContextManager : IContextService
|
||||
public class ContextService : IContextService
|
||||
{
|
||||
private string _currentContext = "Common";
|
||||
|
||||
@@ -6,12 +6,12 @@ using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lattice.Core.Engine;
|
||||
namespace Lattice.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления макетом.
|
||||
/// </summary>
|
||||
public class LayoutManager : ILayoutService
|
||||
public class LayoutService : ILayoutService
|
||||
{
|
||||
private readonly ILogger? _logger;
|
||||
private LayoutNode? _root;
|
||||
@@ -22,7 +22,7 @@ public class LayoutManager : ILayoutService
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler? LayoutUpdated;
|
||||
|
||||
public LayoutManager(ILogger<LayoutManager>? logger = null)
|
||||
public LayoutService(ILogger<LayoutService>? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
19
Lattice.Core/Services/NotificationService.cs
Normal file
19
Lattice.Core/Services/NotificationService.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
|
||||
namespace Lattice.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Простая реализация сервиса уведомлений.
|
||||
/// Хранит только событие и вызывает его при Show().
|
||||
/// </summary>
|
||||
public sealed class NotificationService : INotificationService
|
||||
{
|
||||
public event EventHandler<NotificationEventArgs>? NotificationReceived;
|
||||
|
||||
public void Show(string message, NotificationSeverity severity = NotificationSeverity.Info, int durationSeconds = 5)
|
||||
{
|
||||
NotificationReceived?.Invoke(this, new NotificationEventArgs(message, severity, durationSeconds));
|
||||
}
|
||||
}
|
||||
64
Lattice.Studio/Controls/LatticeStudioShell.xaml
Normal file
64
Lattice.Studio/Controls/LatticeStudioShell.xaml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<UserControl
|
||||
x:Class="Lattice.Studio.Controls.LatticeStudioShell"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.Studio.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:lui="using:Lattice.UI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<!-- TitleBar с MenuBar -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- Тулбар -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- СЛОТ ДЛЯ УВЕДОМЛЕНИЙ -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- Докинг -->
|
||||
<RowDefinition Height="*" />
|
||||
<!-- Статус-бар -->
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Нативный TitleBar Windows 11 -->
|
||||
<TitleBar x:Name="AppTitleBar"
|
||||
Title="{x:Bind Title, Mode=OneWay}"
|
||||
Subtitle="{x:Bind Subtitle, Mode=OneWay}"
|
||||
IsBackButtonVisible="False"
|
||||
IconSource="{x:Bind TitleBarIcon, Mode=OneWay}"
|
||||
IsPaneToggleButtonVisible="False">
|
||||
|
||||
<!-- Вставляем меню прямо в заголовок -->
|
||||
<TitleBar.Content>
|
||||
<Grid x:Name="TitleBarContentGrid" Margin="8,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- Меню -->
|
||||
<ColumnDefinition Width="*" />
|
||||
<!-- Зона перетаскивания -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Слот для MenuBar -->
|
||||
<ContentPresenter x:Name="MenuSlot"
|
||||
Content="{x:Bind MenuContent, Mode=OneWay}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!-- Пустая область для захвата мышью (Drag Region) -->
|
||||
<Canvas Grid.Column="1" Background="Transparent" />
|
||||
</Grid>
|
||||
</TitleBar.Content>
|
||||
</TitleBar>
|
||||
|
||||
<!-- Тулбар -->
|
||||
<lui:LatticeContextualToolbar Grid.Row="1" x:Name="StudioToolbar" />
|
||||
<!-- Контейнер для уведомлений (StackPanel для нескольких сообщений) -->
|
||||
<StackPanel Grid.Row="2" x:Name="NotificationArea" VerticalAlignment="Top" Canvas.ZIndex="100" />
|
||||
<!-- Докинг -->
|
||||
<lui:LatticeDockHost Grid.Row="3" x:Name="MainDockHost" />
|
||||
<!-- Статус-бар -->
|
||||
<ContentPresenter Grid.Row="4" Content="{x:Bind StatusContent, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
144
Lattice.Studio/Controls/LatticeStudioShell.xaml.cs
Normal file
144
Lattice.Studio/Controls/LatticeStudioShell.xaml.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Lattice.Studio.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Îñíîâíàÿ îáîëî÷êà ïðèëîæåíèÿ â ñòèëå Visual Studio 2026.
|
||||
/// Îáåñïå÷èâàåò èíòåãðàöèþ íàòèâíîãî çàãîëîâêà, ñèñòåìû äîêèíãà è êîíòåêñòíûõ êîìàíä.
|
||||
/// </summary>
|
||||
public sealed partial class LatticeStudioShell : UserControl
|
||||
{
|
||||
private ILayoutService? _layoutManager;
|
||||
private IContextService? _contextService;
|
||||
private IEnumerable<ActionDefinition>? _allActions;
|
||||
|
||||
#region Dependency Properties (Ñëîòû êàñòîìèçàöèè)
|
||||
|
||||
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(LatticeStudioShell), new PropertyMetadata("Lattice IDE"));
|
||||
|
||||
public string Subtitle { get => (string)GetValue(SubtitleProperty); set => SetValue(SubtitleProperty, value); }
|
||||
public static readonly DependencyProperty SubtitleProperty =
|
||||
DependencyProperty.Register(nameof(Subtitle), typeof(string), typeof(LatticeStudioShell), new PropertyMetadata(string.Empty));
|
||||
|
||||
public IconSource TitleBarIcon { get => (IconSource)GetValue(TitleBarIconProperty); set => SetValue(TitleBarIconProperty, value); }
|
||||
public static readonly DependencyProperty TitleBarIconProperty =
|
||||
DependencyProperty.Register(nameof(TitleBarIcon), typeof(IconSource), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||
|
||||
public object MenuContent { get => GetValue(MenuContentProperty); set => SetValue(MenuContentProperty, value); }
|
||||
public static readonly DependencyProperty MenuContentProperty =
|
||||
DependencyProperty.Register(nameof(MenuContent), typeof(object), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||
|
||||
public object StatusContent { get => GetValue(StatusContentProperty); set => SetValue(StatusContentProperty, value); }
|
||||
public static readonly DependencyProperty StatusContentProperty =
|
||||
DependencyProperty.Register(nameof(StatusContent), typeof(object), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||
|
||||
#endregion
|
||||
|
||||
public LatticeStudioShell()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Èíèöèàëèçèðóåò îáîëî÷êó Studio è ñâÿçûâàåò å¸ ñ ñåðâèñàìè Lattice.Core.
|
||||
/// </summary>
|
||||
/// <param name="layoutManager">Ýêçåìïëÿð ILayoutService äëÿ óïðàâëåíèÿ îêíàìè.</param>
|
||||
/// <param name="contextService">Ýêçåìïëÿð IContextService äëÿ óïðàâëåíèÿ êíîïêàìè òóëáàðà.</param>
|
||||
/// <param name="actions">Ïîëíûé ñïèñîê îïðåäåëåíèé êîìàíä (ActionDefinition).</param>
|
||||
public void Initialize(ILayoutService layoutManager, IContextService contextService, IEnumerable<ActionDefinition> actions)
|
||||
{
|
||||
_layoutManager = layoutManager;
|
||||
_contextService = contextService;
|
||||
_allActions = actions;
|
||||
|
||||
// Ñâÿçûâàåì âèçóàëüíûé õîñò äîêèíãà ñ ëîãè÷åñêèì äâèæêîì
|
||||
MainDockHost.Manager = _layoutManager;
|
||||
|
||||
// Ïîäïèñûâàåìñÿ íà ñìåíó êîíòåêñòà (âûçûâàåòñÿ ïðè ïåðåêëþ÷åíèè âêëàäîê â Lattice.UI)
|
||||
_contextService.ContextChanged += (s, newContext) =>
|
||||
{
|
||||
// Îáíîâëÿåì òóëáàð â ïîòîêå ïîëüçîâàòåëüñêîãî èíòåðôåéñà
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
StudioToolbar.UpdateItems(_allActions, newContext);
|
||||
});
|
||||
};
|
||||
|
||||
// Íà÷àëüíàÿ èíèöèàëèçàöèÿ òóëáàðà òåêóùèì êîíòåêñòîì
|
||||
StudioToolbar.UpdateItems(_allActions, _contextService.CurrentContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Íàñòðàèâàåò èíòåãðàöèþ ñ íàòèâíûì îêíîì Windows 11 (TitleBar è Backdrop).
|
||||
/// Âûçûâàåòñÿ èç MainWindow ïðèëîæåíèÿ.
|
||||
/// </summary>
|
||||
/// <param name="window">Òåêóùåå îêíî WinUI 3.</param>
|
||||
public void SetupWindow(Window window)
|
||||
{
|
||||
// 1. Ðàñøèðÿåì êîíòåíò â îáëàñòü çàãîëîâêà (Windowing SDK 1.8)
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
// 2. Óêàçûâàåì íàòèâíûé TitleBar äëÿ óïðàâëåíèÿ ïåðåòàñêèâàíèåì îêíà
|
||||
if (AppTitleBar != null)
|
||||
{
|
||||
window.SetTitleBar(AppTitleBar);
|
||||
}
|
||||
|
||||
// 3. Ïðèìåíÿåì Mica Alt äëÿ ôîíà â ñòèëå Visual Studio 2026
|
||||
window.SystemBackdrop = new MicaBackdrop()
|
||||
{
|
||||
Kind = MicaKind.BaseAlt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ïîäêëþ÷àåò ñåðâèñ óâåäîìëåíèé ê îáîëî÷êå.
|
||||
/// </summary>
|
||||
public void InitializeNotifications(INotificationService notificationService)
|
||||
{
|
||||
notificationService.NotificationReceived += (s, e) =>
|
||||
{
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
var infoBar = new InfoBar
|
||||
{
|
||||
Message = e.Message,
|
||||
Severity = MapSeverity(e.Severity),
|
||||
IsOpen = true,
|
||||
Style = (Style)Application.Current.Resources["LatticeNotificationStyle"]
|
||||
};
|
||||
|
||||
// Àâòîìàòè÷åñêîå çàêðûòèå
|
||||
if (e.DurationSeconds > 0)
|
||||
{
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(e.DurationSeconds)
|
||||
};
|
||||
timer.Tick += (st, et) => { infoBar.IsOpen = false; timer.Stop(); };
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
// Óäàëåíèå èç ïàìÿòè ïîñëå çàêðûòèÿ
|
||||
infoBar.CloseButtonClick += (sender, args) => NotificationArea.Children.Remove(infoBar);
|
||||
|
||||
NotificationArea.Children.Insert(0, infoBar); // Íîâûå ñâåðõó
|
||||
});
|
||||
};
|
||||
}
|
||||
private InfoBarSeverity MapSeverity(NotificationSeverity severity) => severity switch
|
||||
{
|
||||
NotificationSeverity.Error => InfoBarSeverity.Error,
|
||||
NotificationSeverity.Warning => InfoBarSeverity.Warning,
|
||||
NotificationSeverity.Success => InfoBarSeverity.Success,
|
||||
_ => InfoBarSeverity.Informational
|
||||
};
|
||||
}
|
||||
18
Lattice.Studio/Controls/LatticeStudioWindow.xaml
Normal file
18
Lattice.Studio/Controls/LatticeStudioWindow.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Window
|
||||
x:Class="Lattice.Studio.Controls.LatticeStudioWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.Studio.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="LatticeStudioWindow">
|
||||
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<local:LatticeStudioShell x:Name="ShellRoot" />
|
||||
|
||||
</Window>
|
||||
116
Lattice.Studio/Controls/LatticeStudioWindow.xaml.cs
Normal file
116
Lattice.Studio/Controls/LatticeStudioWindow.xaml.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.Studio.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Îêíî âåðõíåãî óðîâíÿ, ñîäåðæàùåå LatticeStudioShell.
|
||||
/// Îò íåãî íàñëåäóåòñÿ ðåàëüíîå ïðèëîæåíèå.
|
||||
/// </summary>
|
||||
public partial class LatticeStudioWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Âíóòðåííèé âèçóàëüíûé Shell.
|
||||
/// </summary>
|
||||
public LatticeStudioShell Shell => ShellRoot;
|
||||
|
||||
public LatticeStudioWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Ñòàðòîâûé çàãîëîâîê
|
||||
Title = "Lattice IDE";
|
||||
Shell.Title = Title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ïåðåîïðåäåëÿåì Title, ÷òîáû ñèíõðîíèçèðîâàòü åãî ñ Shell.
|
||||
/// </summary>
|
||||
public new string Title
|
||||
{
|
||||
get => base.Title;
|
||||
set
|
||||
{
|
||||
base.Title = value;
|
||||
if (ShellRoot is not null)
|
||||
{
|
||||
ShellRoot.Title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Ïðîêñèðóåì ñâîéñòâà Shell íàðóæó
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
public string Subtitle
|
||||
{
|
||||
get => Shell.Subtitle;
|
||||
set => Shell.Subtitle = value;
|
||||
}
|
||||
|
||||
public IconSource TitleBarIcon
|
||||
{
|
||||
get => Shell.TitleBarIcon;
|
||||
set => Shell.TitleBarIcon = value;
|
||||
}
|
||||
|
||||
public object MenuContent
|
||||
{
|
||||
get => Shell.MenuContent;
|
||||
set => Shell.MenuContent = value;
|
||||
}
|
||||
|
||||
public object StatusContent
|
||||
{
|
||||
get => Shell.StatusContent;
|
||||
set => Shell.StatusContent = value;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Èíèöèàëèçàöèÿ ñåðâèñîâ Lattice.Core
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Åäèíàÿ òî÷êà èíèöèàëèçàöèè îêíà.
|
||||
/// Âûçûâàåòñÿ èç êîíñòðóêòîðà íàñëåäíèêà (MainWindow).
|
||||
/// </summary>
|
||||
public void Initialize(
|
||||
ILayoutService layoutService,
|
||||
IContextService contextService,
|
||||
IEnumerable<ActionDefinition> actions,
|
||||
INotificationService notificationService)
|
||||
{
|
||||
Shell.Initialize(layoutService, contextService, actions);
|
||||
Shell.InitializeNotifications(notificationService);
|
||||
Shell.SetupWindow(this);
|
||||
|
||||
OnShellInitialized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Èíèöèàëèçèðóåò îêíî ñòàíäàðòíûìè ðåàëèçàöèÿìè ñåðâèñîâ Lattice.Core.
|
||||
/// Ïîäõîäèò äëÿ áûñòðûõ ïðîòîòèïîâ è ïðîñòûõ ïðèëîæåíèé.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
var layout = new LayoutService();
|
||||
var context = new ContextService();
|
||||
var notifications = new NotificationService();
|
||||
|
||||
// Ïóñòîé íàáîð êîìàíä ïî óìîë÷àíèþ
|
||||
var actions = Enumerable.Empty<ActionDefinition>();
|
||||
|
||||
Initialize(layout, context, actions, notifications);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Õóê äëÿ íàñëåäíèêîâ — âûçûâàåòñÿ ïîñëå ïîëíîé èíèöèàëèçàöèè Shell.
|
||||
/// </summary>
|
||||
protected virtual void OnShellInitialized()
|
||||
{
|
||||
}
|
||||
}
|
||||
45
Lattice.Studio/Lattice.Studio.csproj
Normal file
45
Lattice.Studio/Lattice.Studio.csproj
Normal file
@@ -0,0 +1,45 @@
|
||||
<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>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Lattice.Studio</AssemblyName>
|
||||
<RootNamespace>Lattice.Studio</RootNamespace>
|
||||
|
||||
<Authors>FrigaT</Authors>
|
||||
<Company>FrigaT</Company>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\LatticeStudioWindow.xaml" />
|
||||
<None Remove="Themes\StudioThemes.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lattice.Core\Lattice.Core.csproj" />
|
||||
<ProjectReference Include="..\Lattice.UI\Lattice.UI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\LatticeStudioWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Themes\StudioThemes.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
182
Lattice.Studio/README.md
Normal file
182
Lattice.Studio/README.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# **Lattice.Studio**
|
||||
|
||||
`Lattice.Studio` — это модуль, предоставляющий готовую визуальную оболочку для приложений на WinUI 3, оформленную в стиле Visual Studio 2026.
|
||||
Он включает:
|
||||
|
||||
- нативный Windows 11 TitleBar,
|
||||
- слот для меню,
|
||||
- контекстный тулбар,
|
||||
- область уведомлений,
|
||||
- хост для докинга,
|
||||
- статус‑бар,
|
||||
- тему оформления (Dark/Light),
|
||||
- и главное — **готовое окно верхнего уровня `LatticeStudioWindow`**.
|
||||
|
||||
`LatticeStudioShell` служит визуальным контейнером, а `LatticeStudioWindow` — удобной точкой входа для реальных приложений.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Состав модуля
|
||||
|
||||
### **1. LatticeStudioShell.xaml**
|
||||
Определяет визуальную структуру IDE‑оболочки:
|
||||
|
||||
- **TitleBar**:
|
||||
- заголовок,
|
||||
- иконка (`IconSource`),
|
||||
- слот для меню (`MenuContent`),
|
||||
- drag‑region.
|
||||
- **LatticeContextualToolbar** — контекстный тулбар.
|
||||
- **NotificationArea** — стек уведомлений.
|
||||
- **LatticeDockHost** — хост для системы докинга.
|
||||
- **StatusContent** — слот для статус‑бара.
|
||||
|
||||
### **2. LatticeStudioShell.xaml.cs**
|
||||
Реализует логику оболочки:
|
||||
|
||||
- интеграция с сервисами:
|
||||
- `ILayoutService`,
|
||||
- `IContextService`,
|
||||
- `INotificationService`;
|
||||
- обновление тулбара при смене контекста;
|
||||
- отображение уведомлений через `InfoBar`;
|
||||
- настройка окна:
|
||||
- `ExtendsContentIntoTitleBar`,
|
||||
- кастомный TitleBar,
|
||||
- Mica Alt backdrop.
|
||||
|
||||
### **3. LatticeStudioWindow.xaml / .cs**
|
||||
Готовое окно верхнего уровня:
|
||||
|
||||
- содержит `LatticeStudioShell`,
|
||||
- проксирует его свойства наружу:
|
||||
- `Title`,
|
||||
- `Subtitle`,
|
||||
- `MenuContent`,
|
||||
- `StatusContent`,
|
||||
- `TitleBarIcon`,
|
||||
- предоставляет удобный метод инициализации:
|
||||
- `Initialize(...)` — с пользовательскими сервисами,
|
||||
- `Initialize()` — со стандартными реализациями.
|
||||
|
||||
Используется как базовый класс для `MainWindow`.
|
||||
|
||||
### **4. Themes/StudioThemes.xaml**
|
||||
Содержит тему оформления:
|
||||
|
||||
- цвета панелей,
|
||||
- цвета заголовков,
|
||||
- акцентные цвета,
|
||||
- поддержка Light/Dark.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Возможности
|
||||
|
||||
- Готовая IDE‑оболочка для WinUI 3.
|
||||
- Интеграция с Lattice.Core:
|
||||
- докинг,
|
||||
- контексты,
|
||||
- команды.
|
||||
- Нативный Windows 11 TitleBar.
|
||||
- Встроенная система уведомлений.
|
||||
- Слоты для меню и статус‑бара.
|
||||
- Тема в стиле Visual Studio 2026.
|
||||
- Готовое окно `LatticeStudioWindow` для быстрого старта.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
## 🅰 Вариант 1 — минимальный XAML (всё в C#)
|
||||
|
||||
### **MainWindow.xaml**
|
||||
|
||||
```xml
|
||||
<studio:LatticeStudioWindow
|
||||
x:Class="MyApp.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:studio="using:Lattice.Studio.Controls">
|
||||
</studio:LatticeStudioWindow>
|
||||
```
|
||||
|
||||
### **MainWindow.xaml.cs**
|
||||
|
||||
```csharp
|
||||
public sealed partial class MainWindow : LatticeStudioWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Title = "My App";
|
||||
|
||||
MenuContent = BuildMenu();
|
||||
StatusContent = new TextBlock { Text = "Ready" };
|
||||
|
||||
Initialize(); // стандартные сервисы
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🅱 Вариант 2 — декларативный XAML (меню/статус‑бар в XAML)
|
||||
|
||||
### **MainWindow.xaml**
|
||||
|
||||
```xml
|
||||
<studio:LatticeStudioWindow
|
||||
x:Class="MyApp.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:studio="using:Lattice.Studio.Controls">
|
||||
|
||||
<studio:LatticeStudioWindow.MenuContent>
|
||||
<MenuBar>
|
||||
<MenuBarItem Title="File">
|
||||
<MenuFlyoutItem Text="New" />
|
||||
<MenuFlyoutItem Text="Open" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem Text="Exit" />
|
||||
</MenuBarItem>
|
||||
</MenuBar>
|
||||
</studio:LatticeStudioWindow.MenuContent>
|
||||
|
||||
<studio:LatticeStudioWindow.StatusContent>
|
||||
<TextBlock Text="Ready" Margin="8,0"/>
|
||||
</studio:LatticeStudioWindow.StatusContent>
|
||||
|
||||
</studio:LatticeStudioWindow>
|
||||
```
|
||||
|
||||
### **MainWindow.xaml.cs**
|
||||
|
||||
```csharp
|
||||
public sealed partial class MainWindow : LatticeStudioWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Title = "My App";
|
||||
Initialize(); // стандартные сервисы
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Инициализация вручную
|
||||
|
||||
Если нужны свои сервисы:
|
||||
|
||||
```csharp
|
||||
Initialize(layoutService, contextService, actions, notificationService);
|
||||
```
|
||||
|
||||
Если нужен быстрый старт:
|
||||
|
||||
```csharp
|
||||
Initialize();
|
||||
```
|
||||
22
Lattice.Studio/Themes/StudioThemes.xaml
Normal file
22
Lattice.Studio/Themes/StudioThemes.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
>
|
||||
|
||||
<!-- Цвета Visual Studio 2026 (Dark) -->
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="LatticeStatusBarBackground" Color="#FF007ACC"/>
|
||||
<SolidColorBrush x:Key="LatticePaneHeaderBackground" Color="#FF2D2D30"/>
|
||||
<SolidColorBrush x:Key="LatticePaneBackground" Color="#FF1E1E1E"/>
|
||||
<SolidColorBrush x:Key="LatticeActiveHeaderBrush" Color="#FF007ACC"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="LatticeStatusBarBackground" Color="#FF007ACC"/>
|
||||
<SolidColorBrush x:Key="LatticePaneHeaderBackground" Color="#FFEEEEF2"/>
|
||||
<SolidColorBrush x:Key="LatticePaneBackground" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="LatticeActiveHeaderBrush" Color="#FF007ACC"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Lattice.UI/DragDrop/DockTabHandler.cs
Normal file
145
Lattice.UI/DragDrop/DockTabHandler.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Lattice.UI.Controls;
|
||||
using Lattice.UI.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Lattice.UI.DragDrop;
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик перетаскивания панелей и вкладок для системы Lattice.
|
||||
/// </summary>
|
||||
public class DockTabHandler
|
||||
{
|
||||
private bool _isDragging;
|
||||
private readonly LatticeDockHost _host;
|
||||
|
||||
// Состояние текущей операции перетаскивания
|
||||
private LayoutNode? _sourceNode;
|
||||
private LayoutNode? _targetNode;
|
||||
private DockDirection _currentSide;
|
||||
|
||||
public DockTabHandler(LatticeDockHost host)
|
||||
{
|
||||
_host = host;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Привязывает логику перетаскивания к визуальному элементу (заголовку панели).
|
||||
/// </summary>
|
||||
/// <param name="header">Элемент, за который пользователь "хватает" панель.</param>
|
||||
/// <param name="node">Узел макета, связанный с этой панелью.</param>
|
||||
public void Attach(FrameworkElement header, LayoutNode node)
|
||||
{
|
||||
header.PointerPressed += (s, e) =>
|
||||
{
|
||||
_isDragging = true;
|
||||
_sourceNode = node;
|
||||
header.CapturePointer(e.Pointer);
|
||||
|
||||
if (_host.AnchorOverlay != null)
|
||||
_host.AnchorOverlay.Visibility = Visibility.Visible;
|
||||
};
|
||||
|
||||
header.PointerMoved += (s, e) =>
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
// Получаем позицию курсора относительно всего хоста
|
||||
Point pointerPos = e.GetCurrentPoint(_host).Position;
|
||||
UpdateOverlayPosition(pointerPos);
|
||||
};
|
||||
|
||||
header.PointerReleased += (s, e) =>
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
_isDragging = false;
|
||||
header.ReleasePointerCapture(e.Pointer);
|
||||
CompleteDocking();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет положение визуальных подсказок и рассчитывает зоны сброса.
|
||||
/// </summary>
|
||||
private void UpdateOverlayPosition(Point pointerPosition)
|
||||
{
|
||||
var overlay = _host.AnchorOverlay;
|
||||
if (overlay == null) return;
|
||||
|
||||
// 1. Позиционируем "ромб" с кнопками докинга
|
||||
overlay.PositionAnchors(pointerPosition);
|
||||
|
||||
// 2. Хит-тестинг: ищем LatticePane под курсором (исключая саму перетаскиваемую панель)
|
||||
var elements = VisualTreeHelper.FindElementsInHostCoordinates(pointerPosition, _host);
|
||||
var targetPane = elements.OfType<LatticePane>()
|
||||
.FirstOrDefault(p => (p.DataContext as LayoutNode)?.Id != _sourceNode?.Id);
|
||||
|
||||
if (targetPane != null && targetPane.DataContext is LayoutNode targetNode)
|
||||
{
|
||||
_targetNode = targetNode;
|
||||
|
||||
// 3. Расчет локальной позиции для определения стороны
|
||||
var transform = targetPane.TransformToVisual(_host);
|
||||
Point localPoint = transform.Inverse.TransformPoint(pointerPosition);
|
||||
|
||||
// 4. Определяем сторону через сервис
|
||||
_currentSide = VisualTreeService.GetHitZone(targetPane, localPoint);
|
||||
|
||||
// 5. Показываем синее превью зоны сброса
|
||||
Rect previewRect = CalculatePreviewRect(targetPane, _currentSide);
|
||||
Rect globalPreviewRect = transform.TransformBounds(previewRect);
|
||||
|
||||
overlay.ShowPreview(globalPreviewRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetNode = null;
|
||||
overlay.HidePreview();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитывает прямоугольник предпросмотра на основе выбранной стороны.
|
||||
/// </summary>
|
||||
private Rect CalculatePreviewRect(FrameworkElement pane, DockDirection side)
|
||||
{
|
||||
double w = pane.ActualWidth;
|
||||
double h = pane.ActualHeight;
|
||||
|
||||
return side switch
|
||||
{
|
||||
DockDirection.Left => new Rect(0, 0, w / 2, h),
|
||||
DockDirection.Right => new Rect(w / 2, 0, w / 2, h),
|
||||
DockDirection.Top => new Rect(0, 0, w, h / 2),
|
||||
DockDirection.Bottom => new Rect(0, h / 2, w, h / 2),
|
||||
_ => new Rect(0, 0, w, h) // Center
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Завершает операцию докинга, передавая данные в Core Engine.
|
||||
/// </summary>
|
||||
private void CompleteDocking()
|
||||
{
|
||||
if (_sourceNode != null && _targetNode != null && _host.Manager != null)
|
||||
{
|
||||
// Вызываем логику перестроения дерева в Lattice.Core
|
||||
_host.Manager.Dock(_sourceNode, _targetNode, _currentSide);
|
||||
}
|
||||
|
||||
// Очистка UI
|
||||
var overlay = _host.AnchorOverlay;
|
||||
if (overlay != null)
|
||||
{
|
||||
overlay.Visibility = Visibility.Collapsed;
|
||||
overlay.HidePreview();
|
||||
}
|
||||
|
||||
_sourceNode = null;
|
||||
_targetNode = null;
|
||||
}
|
||||
}
|
||||
36
Lattice.UI/Lattice.UI.csproj
Normal file
36
Lattice.UI/Lattice.UI.csproj
Normal file
@@ -0,0 +1,36 @@
|
||||
<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>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Lattice.UI</AssemblyName>
|
||||
<RootNamespace>Lattice.UI</RootNamespace>
|
||||
|
||||
<Authors>FrigaT</Authors>
|
||||
<Company>FrigaT</Company>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Themes\Styles\LatticeNotification.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lattice.Core\Lattice.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Themes\Styles\LatticeNotification.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
71
Lattice.UI/Primitives/DockAnchorOverlay.cs
Normal file
71
Lattice.UI/Primitives/DockAnchorOverlay.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
|
||||
namespace Lattice.UI.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальный оверлей, отображающий зоны приземления (Drop Zones) и якоря докинга.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = "OverlayCanvas", Type = typeof(Canvas))]
|
||||
[TemplatePart(Name = "DropPreview", Type = typeof(Rectangle))]
|
||||
[TemplatePart(Name = "AnchorGroup", Type = typeof(Grid))]
|
||||
public class DockAnchorOverlay : Control
|
||||
{
|
||||
private Canvas? _overlayCanvas;
|
||||
private Rectangle? _dropPreview;
|
||||
private Grid? _anchorGroup;
|
||||
|
||||
public DockAnchorOverlay()
|
||||
{
|
||||
// Привязываем стиль из Generic.xaml
|
||||
this.DefaultStyleKey = typeof(DockAnchorOverlay);
|
||||
|
||||
// По умолчанию скрыт, показывается только во время Drag-and-Drop
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_overlayCanvas = GetTemplateChild("OverlayCanvas") as Canvas;
|
||||
_dropPreview = GetTemplateChild("DropPreview") as Rectangle;
|
||||
_anchorGroup = GetTemplateChild("AnchorGroup") as Grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отображает превью будущей зоны закрепления.
|
||||
/// </summary>
|
||||
/// <param name="rect">Координаты и размер зоны.</param>
|
||||
public void ShowPreview(Windows.Foundation.Rect rect)
|
||||
{
|
||||
if (_dropPreview == null) return;
|
||||
|
||||
_dropPreview.Visibility = Visibility.Visible;
|
||||
Canvas.SetLeft(_dropPreview, rect.X);
|
||||
Canvas.SetTop(_dropPreview, rect.Y);
|
||||
_dropPreview.Width = rect.Width;
|
||||
_dropPreview.Height = rect.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Скрывает превью зоны.
|
||||
/// </summary>
|
||||
public void HidePreview()
|
||||
{
|
||||
if (_dropPreview != null)
|
||||
_dropPreview.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Центрирует группу якорей (ромб) относительно указанной точки.
|
||||
/// </summary>
|
||||
public void PositionAnchors(Windows.Foundation.Point centerPoint)
|
||||
{
|
||||
if (_anchorGroup == null) return;
|
||||
|
||||
Canvas.SetLeft(_anchorGroup, centerPoint.X - (_anchorGroup.Width / 2));
|
||||
Canvas.SetTop(_anchorGroup, centerPoint.Y - (_anchorGroup.Height / 2));
|
||||
}
|
||||
}
|
||||
16
Lattice.UI/Primitives/LatticeIcon.cs
Normal file
16
Lattice.UI/Primitives/LatticeIcon.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Lattice.UI.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// Утилита для быстрого получения иконок в стиле Fluent UI 2.
|
||||
/// </summary>
|
||||
public static class LatticeIcon
|
||||
{
|
||||
public static FontIcon GetIcon(string glyph) => new FontIcon
|
||||
{
|
||||
Glyph = glyph,
|
||||
FontFamily = new FontFamily("Segoe Fluent Icons")
|
||||
};
|
||||
}
|
||||
121
Lattice.UI/Primitives/LayoutPanel.cs
Normal file
121
Lattice.UI/Primitives/LayoutPanel.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Lattice.UI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Lattice.UI.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// Кастомный контейнер, преобразующий иерархию узлов Lattice в визуальные элементы WinUI 3.
|
||||
/// </summary>
|
||||
public class LayoutPanel : Grid
|
||||
{
|
||||
private readonly LatticeDockHost _host;
|
||||
|
||||
/// <summary>
|
||||
/// Создает новый экземпляр панели компоновки.
|
||||
/// </summary>
|
||||
/// <param name="host">Корневой хост, управляющий макетом.</param>
|
||||
public LayoutPanel(LatticeDockHost host)
|
||||
{
|
||||
_host = host;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет рекурсивную отрисовку дерева узлов.
|
||||
/// </summary>
|
||||
public void Build(LayoutNode node)
|
||||
{
|
||||
this.Children.Clear();
|
||||
this.ColumnDefinitions.Clear();
|
||||
this.RowDefinitions.Clear();
|
||||
|
||||
if (node is SplitContainerNode splitContainer)
|
||||
{
|
||||
RenderSplit(splitContainer);
|
||||
}
|
||||
else if (node is ContentNode contentNode)
|
||||
{
|
||||
RenderContent(contentNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderSplit(SplitContainerNode container)
|
||||
{
|
||||
for (int i = 0; i < container.Children.Count; i++)
|
||||
{
|
||||
var child = container.Children[i];
|
||||
var childPresenter = new LayoutPanel(_host);
|
||||
|
||||
if (container.Orientation == SplitOrientation.Horizontal)
|
||||
{
|
||||
this.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = child.IsWidthStar ? new GridLength(child.WidthValue, GridUnitType.Star) : new GridLength(child.WidthValue)
|
||||
});
|
||||
Grid.SetColumn(childPresenter, this.ColumnDefinitions.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RowDefinitions.Add(new RowDefinition
|
||||
{
|
||||
Height = child.IsHeightStar ? new GridLength(child.HeightValue, GridUnitType.Star) : new GridLength(child.HeightValue)
|
||||
});
|
||||
Grid.SetRow(childPresenter, this.RowDefinitions.Count - 1);
|
||||
}
|
||||
|
||||
this.Children.Add(childPresenter);
|
||||
childPresenter.Build(child);
|
||||
|
||||
// Добавляем сплиттер между элементами (кроме последнего)
|
||||
if (i < container.Children.Count - 1)
|
||||
{
|
||||
AddSplitter(container, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSplitter(SplitContainerNode container, int index)
|
||||
{
|
||||
var splitter = new LatticeSplitter
|
||||
{
|
||||
LeftNode = container.Children[index],
|
||||
RightNode = container.Children[index + 1],
|
||||
Orientation = container.Orientation,
|
||||
};
|
||||
|
||||
double thickness = (double)Application.Current.Resources["LatticeSplitterThickness"];
|
||||
|
||||
if (container.Orientation == SplitOrientation.Horizontal)
|
||||
{
|
||||
// Сплиттер занимает очень узкую колонку между основными
|
||||
this.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(thickness) });
|
||||
Grid.SetColumn(splitter, this.ColumnDefinitions.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RowDefinitions.Add(new RowDefinition { Height = new GridLength(thickness) });
|
||||
Grid.SetRow(splitter, this.RowDefinitions.Count - 1);
|
||||
}
|
||||
|
||||
this.Children.Add(splitter);
|
||||
}
|
||||
|
||||
private void RenderContent(ContentNode node)
|
||||
{
|
||||
var pane = new LatticePane
|
||||
{
|
||||
Title = node.Name,
|
||||
Content = node.Component,
|
||||
DataContext = node,
|
||||
};
|
||||
|
||||
pane.CloseClick += (s, e) =>
|
||||
{
|
||||
_host.Manager?.Remove(node);
|
||||
};
|
||||
|
||||
this.Children.Add(pane);
|
||||
}
|
||||
}
|
||||
56
Lattice.UI/README.md
Normal file
56
Lattice.UI/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Lattice.UI
|
||||
|
||||
[](#)
|
||||
[](git.frigat.duckdns.org)
|
||||
[](#)
|
||||
|
||||
**Lattice.UI** — это библиотека нативных элементов управления WinUI 3, реализующая сложную систему докинга и управления окнами в стиле Visual Studio 2026. Она визуализирует абстрактное дерево компоновки из `Lattice.Core` и обеспечивает плавное взаимодействие пользователя с интерфейсом.
|
||||
|
||||
## ✨ Основные компоненты
|
||||
|
||||
- **LatticeDockHost**: Корневой оркестратор, управляющий слоями контента и визуальными подсказками докинга.
|
||||
- **LatticePane**: Универсальный контейнер для панелей и документов с поддержкой заголовков, контекстных кнопок и закрытия.
|
||||
- **LatticeSplitter**: Тонкий интерактивный разделитель для динамического изменения размеров областей.
|
||||
- **DockAnchorOverlay**: Система визуальных подсказок («ромб» докинга) и превью зон сброса (Drop Zones).
|
||||
- **LatticeContextualToolbar**: Адаптивная панель инструментов, автоматически меняющая набор кнопок при смене фокуса между вкладками.
|
||||
|
||||
## 🛠 Технологии
|
||||
|
||||
- **Windows App SDK 1.8+**: Использование последних достижений WinUI 3.
|
||||
- **Fluent UI 2**: Дизайн, полностью соответствующий стандартам Windows 11 (Mica Alt, закругления 4-8px, Segoe Fluent Icons).
|
||||
- **Design Tokens**: Полная темизация через систему ресурсов (`SharedResources.xaml`).
|
||||
|
||||
## 📦 Установка и настройка
|
||||
|
||||
1. Добавьте ссылку на проект `Lattice.UI` в ваше решение.
|
||||
2. В файле `App.xaml` вашего приложения подключите стили библиотеки:
|
||||
|
||||
```xml
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Generic.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### 🚀 Быстрый старт (XAML)
|
||||
```
|
||||
<Window
|
||||
xmlns:lattice="using:Lattice.UI.Controls">
|
||||
|
||||
<lattice:LatticeDockHost x:Name="MainHost"
|
||||
Manager="{x:Bind ViewModel.LayoutManager}" />
|
||||
</Window>
|
||||
```
|
||||
|
||||
### 📐 Математика докинга
|
||||
- Библиотека использует алгоритм «Конверта» для расчета зон приземления:
|
||||
- Центр: Объединение в группу вкладок.
|
||||
- Края (L/R/T/B): Разделение текущей области на две части в соответствующей ориентации.
|
||||
|
||||
### 🔗 Ссылки
|
||||
- Core Engine: Lattice.Core
|
||||
- Репозиторий: git.frigat.duckdns.org
|
||||
- Разработчик: FrigaT
|
||||
52
Lattice.UI/Services/VisualTreeService.cs
Normal file
52
Lattice.UI/Services/VisualTreeService.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Lattice.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для анализа визуального дерева и расчета зон взаимодействия.
|
||||
/// </summary>
|
||||
public static class VisualTreeService
|
||||
{
|
||||
/// <summary>
|
||||
/// Определяет зону докинга на основе позиции курсора относительно элемента.
|
||||
/// </summary>
|
||||
/// <param name="element">Визуальный элемент (панель), над которым находится курсор.</param>
|
||||
/// <param name="relativePoint">Координаты курсора относительно левого верхнего угла элемента.</param>
|
||||
/// <returns>Направление докинга (DockDirection).</returns>
|
||||
public static DockDirection GetHitZone(FrameworkElement element, Point relativePoint)
|
||||
{
|
||||
double w = element.ActualWidth;
|
||||
double h = element.ActualHeight;
|
||||
|
||||
// 1. Зона центра (обычно это 40% центральной области)
|
||||
// Если курсор в центре, вкладка просто добавится в текущий TabView.
|
||||
double centerX = w * 0.3;
|
||||
double centerY = h * 0.3;
|
||||
Rect centerRect = new Rect(centerX, centerY, w * 0.4, h * 0.4);
|
||||
|
||||
if (centerRect.Contains(relativePoint))
|
||||
{
|
||||
return DockDirection.Center;
|
||||
}
|
||||
|
||||
// 2. Расчет по диагоналям для боковых зон
|
||||
// Представьте конверт: линии из углов в центр. Это самый точный способ
|
||||
// определения стороны в стиле Visual Studio.
|
||||
|
||||
// Нормализуем координаты в диапазон от 0 до 1
|
||||
double nx = relativePoint.X / w;
|
||||
double ny = relativePoint.Y / h;
|
||||
|
||||
// Уравнения диагоналей: y = x и y = 1 - x
|
||||
bool isAbovePrimary = ny < nx;
|
||||
bool isAboveSecondary = ny < (1 - nx);
|
||||
|
||||
if (isAbovePrimary && isAboveSecondary) return DockDirection.Top;
|
||||
if (isAbovePrimary && !isAboveSecondary) return DockDirection.Right;
|
||||
if (!isAbovePrimary && isAboveSecondary) return DockDirection.Left;
|
||||
|
||||
return DockDirection.Bottom;
|
||||
}
|
||||
}
|
||||
17
Lattice.UI/Themes/Generic.xaml
Normal file
17
Lattice.UI/Themes/Generic.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.UI.Controls">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/SharedResources.xaml" />
|
||||
|
||||
<!-- Порядок важен: сначала базовые ресурсы, потом стили контролов -->
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeDockHost.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticePane.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeSplitter.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/DockAnchorOverlay.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeNotification.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
40
Lattice.UI/Themes/Styles/DockAnchorOverlay.xaml
Normal file
40
Lattice.UI/Themes/Styles/DockAnchorOverlay.xaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.UI.Primitives">
|
||||
|
||||
<Style TargetType="local:DockAnchorOverlay">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:DockAnchorOverlay">
|
||||
<Canvas x:Name="OverlayCanvas" Background="Transparent">
|
||||
<!-- Центральный "ромб" с кнопками направлений -->
|
||||
<Grid x:Name="AnchorGroup" Width="120" Height="120">
|
||||
<!-- Используем Acrylic или Mica Alt для стиля 2026 -->
|
||||
<Rectangle Fill="{ThemeResource SystemControlAcrylicElementBrush}"
|
||||
RadiusX="4" RadiusY="4" Opacity="0.8"/>
|
||||
|
||||
<!-- Кнопки-иконки (Left, Right, Top, Bottom, Center) -->
|
||||
<FontIcon Glyph="" VerticalAlignment="Top"/>
|
||||
<!-- Top -->
|
||||
<FontIcon Glyph="" HorizontalAlignment="Left"/>
|
||||
<!-- Left -->
|
||||
<FontIcon Glyph="" HorizontalAlignment="Right"/>
|
||||
<!-- Right -->
|
||||
<FontIcon Glyph="" VerticalAlignment="Bottom"/>
|
||||
<!-- Bottom -->
|
||||
<FontIcon Glyph="" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<!-- Center -->
|
||||
</Grid>
|
||||
|
||||
<!-- Превью зоны (синий полупрозрачный прямоугольник) -->
|
||||
<Rectangle x:Name="DropPreview" Fill="{ThemeResource SystemAccentColorLight3}"
|
||||
Opacity="0.4" Visibility="Collapsed"/>
|
||||
</Canvas>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
33
Lattice.UI/Themes/Styles/LatticeDockHost.xaml
Normal file
33
Lattice.UI/Themes/Styles/LatticeDockHost.xaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.UI.Controls"
|
||||
xmlns:primitives="using:Lattice.UI.Primitives">
|
||||
|
||||
<Style TargetType="local:LatticeDockHost">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:LatticeDockHost">
|
||||
<!-- Используем Grid, чтобы наложить оверлей ПОВЕРХ контента -->
|
||||
<Grid x:Name="RootGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<!-- 1. Основной слой: Сюда программно рендерятся панели через LayoutPanel -->
|
||||
<ContentPresenter x:Name="LayoutPresenter"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch" />
|
||||
|
||||
<!-- 2. Слой подсказок: Должен быть ниже в списке, чтобы быть выше визуально -->
|
||||
<!-- Обязательно x:Name="AnchorOverlay", так как C# ищет его по этому имени -->
|
||||
<primitives:DockAnchorOverlay x:Name="AnchorOverlay"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsHitTestVisible="False" />
|
||||
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
15
Lattice.UI/Themes/Styles/LatticeNotification.xaml
Normal file
15
Lattice.UI/Themes/Styles/LatticeNotification.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
>
|
||||
|
||||
<Style x:Key="LatticeNotificationStyle" TargetType="InfoBar">
|
||||
<Setter Property="Margin" Value="12,4" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="IsIconVisible" Value="True" />
|
||||
<!-- В 2026 году используем полупрозрачность Acrylic для уведомлений -->
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
70
Lattice.UI/Themes/Styles/LatticePane.xaml
Normal file
70
Lattice.UI/Themes/Styles/LatticePane.xaml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.UI.Controls">
|
||||
|
||||
<Style TargetType="local:LatticePane">
|
||||
<!-- Используем StaticResource, так как эти токены определены в нашем SharedResources -->
|
||||
<Setter Property="Background" Value="{StaticResource LatticePaneBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource LatticePaneBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{StaticResource LatticePaneMargin}" />
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:LatticePane">
|
||||
<Grid Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{StaticResource LatticePaneCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<!-- Используем токен высоты заголовка -->
|
||||
<RowDefinition Height="Auto" MinHeight="{StaticResource LatticePaneHeaderHeight}" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Заголовок панели -->
|
||||
<Grid x:Name="HeaderPresenter"
|
||||
Background="{StaticResource LatticePaneHeaderBackground}"
|
||||
Padding="8,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{TemplateBinding Title}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
MaxLines="1"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Column="1" VerticalAlignment="Center">
|
||||
<!-- Кастомный контент (кнопки пользователя) -->
|
||||
<ContentPresenter Content="{TemplateBinding HeaderContent}" />
|
||||
|
||||
<!-- Стандартная кнопка закрытия -->
|
||||
<!-- Используем стандартный стиль WinUI "отсутствие рамки", чтобы она выглядела как в VS -->
|
||||
<Button x:Name="PART_CloseButton"
|
||||
Content=""
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="10"
|
||||
Padding="8,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Style="{StaticResource DefaultButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<ContentPresenter Grid.Row="1"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
30
Lattice.UI/Themes/Styles/LatticeSplitter.xaml
Normal file
30
Lattice.UI/Themes/Styles/LatticeSplitter.xaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Lattice.UI.Controls">
|
||||
|
||||
<Style TargetType="local:LatticeSplitter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:LatticeSplitter">
|
||||
<Grid Background="{TemplateBinding Background}">
|
||||
<!-- PART_Thumb — это невидимая или тонкая область, которую тянет пользователь -->
|
||||
<Thumb x:Name="PART_Thumb">
|
||||
<Thumb.Template>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Grid Background="Transparent">
|
||||
<!-- Визуальная линия разделителя -->
|
||||
<Rectangle Fill="{ThemeResource SystemControlForegroundBaseLowBrush}"
|
||||
MinWidth="1" MinHeight="1" HorizontalAlignment="Center"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Thumb.Template>
|
||||
</Thumb>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
26
Lattice.UI/Themes/Styles/SharedResources.xaml
Normal file
26
Lattice.UI/Themes/Styles/SharedResources.xaml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
>
|
||||
|
||||
<!-- Размеры (Tokens) -->
|
||||
<x:Double x:Key="LatticeSplitterThickness">4.0</x:Double>
|
||||
<x:Double x:Key="LatticePaneHeaderHeight">32.0</x:Double>
|
||||
<CornerRadius x:Key="LatticePaneCornerRadius">4</CornerRadius>
|
||||
<Thickness x:Key="LatticePaneMargin">1</Thickness>
|
||||
|
||||
<!-- Акцентный цвет (статичный токен) -->
|
||||
<SolidColorBrush x:Key="LatticeActiveHeaderBrush" Color="{ThemeResource SystemAccentColor}"/>
|
||||
|
||||
<!-- Правильная привязка системных кистей для поддержки смены тем (Dark/Light) -->
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="LatticePaneBackground" ResourceKey="LayerFillColorDefaultBrush"/>
|
||||
<StaticResource x:Key="LatticePaneBorderBrush" ResourceKey="CardStrokeColorDefaultBrush"/>
|
||||
<StaticResource x:Key="LatticePaneHeaderBackground" ResourceKey="LayerOnSecondaryFillColorDefaultBrush"/>
|
||||
</ResourceDictionary>
|
||||
<!-- Можно добавить специфические правки для HighContrast, если нужно -->
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -1,3 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="../../../../../../../../Job/Projects/FrigaT/Lattice/Lattice.Core/Lattice.Core.csproj" />
|
||||
<Project Path="Lattice.Core/Lattice.Core.csproj" />
|
||||
<Project Path="Lattice.Studio/Lattice.Studio.csproj" Id="c866e999-c303-47ae-848c-5a911894ec1d" />
|
||||
<Project Path="Lattice.UI/Lattice.UI.csproj" Id="931bdfd9-8cf2-479a-895e-65facdb0a7ce" />
|
||||
</Solution>
|
||||
|
||||
Reference in New Issue
Block a user