using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Media; using Avalonia.Threading; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; using Markdown.Avalonia; using StabilityMatrix.Avalonia.Controls; namespace StabilityMatrix.Avalonia; public static class DialogHelper { /// /// Create a generic textbox entry content dialog. /// public static BetterContentDialog CreateTextEntryDialog( string title, string description, IReadOnlyList textFields) { Dispatcher.UIThread.VerifyAccess(); var stackPanel = new StackPanel(); var grid = new Grid { RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Star) }, Children = { new TextBlock { Text = description }, stackPanel } }; grid.Loaded += (_, _) => { // Focus first textbox var firstTextBox = stackPanel.Children.OfType().First(); firstTextBox.Focus(); firstTextBox.CaretIndex = firstTextBox.Text?.LastIndexOf('.') ?? 0; }; // Disable primary button if any textboxes are invalid var primaryCommand = new RelayCommand(delegate { }, () => { var invalidCount = textFields.Count(field => !field.IsValid); Debug.WriteLine($"Checking can execute: {invalidCount} invalid fields"); return invalidCount == 0; }); // Create textboxes foreach (var field in textFields) { var label = new TextBlock { Text = field.Label }; stackPanel.Children.Add(label); var textBox = new TextBox { [!TextBox.TextProperty] = new Binding("TextProperty"), Watermark = field.Watermark, DataContext = field, }; stackPanel.Children.Add(textBox); // When IsValid property changes, update invalid count and primary button field.PropertyChanged += (_, args) => { if (args.PropertyName == nameof(TextBoxField.IsValid)) { primaryCommand.NotifyCanExecuteChanged(); } }; // Set initial value textBox.Text = field.Text; // See if initial value is valid try { field.Validator?.Invoke(field.Text); } catch (Exception) { field.IsValid = false; } } return new BetterContentDialog { Title = title, Content = grid, PrimaryButtonText = "OK", CloseButtonText = "Cancel", IsPrimaryButtonEnabled = true, PrimaryButtonCommand = primaryCommand, DefaultButton = ContentDialogButton.Primary }; } /// /// Create a generic dialog for showing a markdown document /// public static BetterContentDialog CreateMarkdownDialog(string markdown, string? title = null) { Dispatcher.UIThread.VerifyAccess(); var viewer = new MarkdownScrollViewer { Markdown = markdown }; return new BetterContentDialog { Title = title, Content = viewer, CloseButtonText = "Close", IsPrimaryButtonEnabled = false, }; } /// /// Create a simple title and description task dialog. /// Sets the XamlRoot to the current top level window. /// public static TaskDialog CreateTaskDialog(string title, string description) { Dispatcher.UIThread.VerifyAccess(); var content = new StackPanel { Children = { new TextBlock { Margin = new Thickness(0,0,0,8), FontSize = 16, Text = title, TextWrapping = TextWrapping.WrapWithOverflow, }, new TextBlock { Text = description, TextWrapping = TextWrapping.WrapWithOverflow, } } }; return new TaskDialog { Title = title, Content = content, XamlRoot = App.VisualRoot }; } } // Text fields public sealed class TextBoxField : INotifyPropertyChanged { // Label above the textbox public string Label { get; init; } = string.Empty; // Actual text value public string Text { get; set; } = string.Empty; // Watermark text public string Watermark { get; init; } = string.Empty; /// /// Validation action on text changes. Throw exception if invalid. /// public Action? Validator { get; init; } public string TextProperty { get => Text; [DebuggerStepThrough] set { try { Validator?.Invoke(value); } catch (Exception e) { IsValid = false; throw new DataValidationException(e.Message); } Text = value; IsValid = true; OnPropertyChanged(); } } // Default to true if no validator is provided private bool isValid; public bool IsValid { get => Validator == null || isValid; set { isValid = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }