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;
}
}