using Lattice.UI.Docking.Abstractions; using Lattice.UI.Docking.Services; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System; using System.Threading.Tasks; namespace Lattice.UI.Docking.WinUI.Services; /// /// Реализация UI-сервиса для платформы WinUI. /// Инкапсулирует платформенно-зависимые операции, такие как создание окон, /// показ диалогов и синхронизация с UI-потоком. /// /// /// /// предоставляет конкретные реализации методов /// для платформы WinUI. Это позволяет основной /// бизнес-логике док-системы оставаться независимой от конкретной UI-платформы. /// /// /// Сервис использует API WinUI для создания окон, показа ContentDialog и /// управления диспетчером потока пользовательского интерфейса. /// /// public sealed class WinUIDockUIService : DockUIServiceBase, IDockUIService { /// /// Создает главное окно приложения для размещения док-хоста. /// /// /// Экземпляр , который будет содержаться в окне. /// /// /// Объект окна WinUI, который можно отобразить и управлять им. /// /// /// Выбрасывается, если равен null. /// /// /// Выбрасывается, если не является элементом WinUI. /// /// /// Создает окно WinUI с заголовком "Lattice IDE", устанавливает указанный хост /// в качестве содержимого и регистрирует окно в системе отслеживания окон. /// Окно создается с настройками по умолчанию для IDE-подобных приложений. /// public override object CreateMainWindow(IDockHost host) { if (host is not FrameworkElement hostElement) throw new ArgumentException("Host must be a FrameworkElement", nameof(host)); var window = new Window(); window.Content = hostElement; window.AppWindow.Title = "Lattice IDE"; // Регистрируем окно в трекере Themes.WindowTracker.Register(window); return window; } /// /// Отображает модальное диалоговое окно с указанным содержимым. /// /// Заголовок диалогового окна. /// Содержимое диалогового окна. /// /// Nullable boolean значение, указывающее результат диалога: /// true - пользователь подтвердил действие, /// false - пользователь отменил действие, /// null - диалог был закрыт без выбора. /// /// /// Выбрасывается, если или равны null. /// /// /// Создает и показывает ContentDialog с кнопками OK и Cancel. /// Блокирует взаимодействие с родительским окном до закрытия диалога. /// Использует XamlRoot активного окна для корректного отображения. /// public override bool? ShowDialog(string title, object content) { if (content is not FrameworkElement contentElement) return null; var dialog = new ContentDialog { Title = title, Content = contentElement, PrimaryButtonText = "OK", CloseButtonText = "Cancel", XamlRoot = GetActiveXamlRoot() }; // Показываем диалог и возвращаем результат var result = dialog.ShowAsync(); return result.GetAwaiter().GetResult() switch { ContentDialogResult.Primary => true, ContentDialogResult.Secondary => false, _ => null }; } /// /// Отображает информационное сообщение с кнопкой OK. /// /// Текст сообщения. /// Заголовок окна сообщения. /// /// Выбрасывается, если или равны null. /// /// /// Создает ContentDialog с текстом сообщения и одной кнопкой OK. /// Используется для информирования пользователя о результате операции /// или отображения некритичных ошибок. /// public override void ShowMessage(string message, string caption) { var dialog = new ContentDialog { Title = caption, Content = new TextBlock { Text = message }, PrimaryButtonText = "OK", XamlRoot = GetActiveXamlRoot() }; dialog.ShowAsync(); } /// /// Отображает диалог подтверждения с кнопками Yes/No. /// /// Текст вопроса. /// Заголовок окна подтверждения. /// /// true, если пользователь выбрал "Yes"; false, если пользователь выбрал "No". /// /// /// Выбрасывается, если или равны null. /// /// /// Создает ContentDialog с кнопками Yes и No. Используется для получения /// подтверждения пользователя перед выполнением критических операций, /// таких как закрытие вкладок с несохраненными данными или сброс настроек. /// public override bool Confirm(string message, string caption) { var dialog = new ContentDialog { Title = caption, Content = new TextBlock { Text = message }, PrimaryButtonText = "Yes", SecondaryButtonText = "No", XamlRoot = GetActiveXamlRoot() }; var result = dialog.ShowAsync().GetAwaiter().GetResult(); return result == ContentDialogResult.Primary; } /// /// Отображает диалог ввода текста. /// /// Текст подсказки для пользователя. /// Значение по умолчанию для поля ввода. /// /// Введенный пользователем текст или null, если диалог был отменен. /// /// /// Выбрасывается, если равен null. /// /// /// Создает ContentDialog с однострочным полем ввода TextBox. /// Используется для получения текстового ввода от пользователя, такого как /// имена файлов, названия документов или параметры конфигурации. /// public override string? Prompt(string prompt, string? defaultValue = null) { var textBox = new TextBox { Text = defaultValue ?? string.Empty, Width = 300, Height = 32 }; var dialog = new ContentDialog { Title = prompt, Content = textBox, PrimaryButtonText = "OK", SecondaryButtonText = "Cancel", XamlRoot = GetActiveXamlRoot() }; var result = dialog.ShowAsync().GetAwaiter().GetResult(); return result == ContentDialogResult.Primary ? textBox.Text : null; } /// /// Выполняет указанное действие в UI-потоке. /// /// Действие для выполнения. /// /// Выбрасывается, если равен null. /// /// /// Гарантирует, что действие будет выполнено в потоке, связанном с /// пользовательским интерфейсом. Если текущий поток уже является UI-потоком, /// действие выполняется немедленно. В противном случае действие ставится /// в очередь диспетчера WinUI. /// public override void InvokeOnUIThread(Action action) { if (action == null) return; var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); if (dispatcherQueue.HasThreadAccess) { action(); } else { dispatcherQueue.TryEnqueue(() => action()); } } /// /// Выполняет указанную асинхронную функцию в UI-потоке. /// /// Асинхронная функция для выполнения. /// /// Задача, представляющая асинхронную операцию. /// /// /// Выбрасывается, если равен null. /// /// /// Гарантирует, что асинхронная функция будет выполнена в UI-потоке. /// Используется для операций, которые требуют доступа к UI-элементам /// или выполняют асинхронные вызовы с обновлением интерфейса. /// public override async Task InvokeOnUIThreadAsync(Func action) { if (action == null) return; var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); if (dispatcherQueue.HasThreadAccess) { await action(); } else { var tcs = new TaskCompletionSource(); dispatcherQueue.TryEnqueue(async () => { try { await action(); tcs.SetResult(true); } catch (Exception ex) { tcs.SetException(ex); } }); await tcs.Task; } } /// /// Получает XamlRoot активного окна приложения. /// /// /// XamlRoot активного окна или null, если нет активных окон. /// /// /// Используется для корректного отображения диалоговых окон в контексте /// текущего окна приложения. Перебирает все зарегистрированные окна /// и возвращает XamlRoot первого найденного. /// private XamlRoot? GetActiveXamlRoot() { // Получаем XamlRoot из активного окна foreach (var window in Themes.WindowTracker.Windows) { if (window.Content is FrameworkElement element) { return element.XamlRoot; } } return null; } }