using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using Avalonia; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform.Storage; using Inspectron.Settings.Attributes; namespace Inspectron.Settings.Avalonia.Configuration; public class DefaultControlFactory : IControlFactory { public virtual Control CreateControl(string name, string description, PropertyInfo propertyInfo, object initialValue, Action valueChangedCallback, OptionsWindow optionsWindow) { var type = propertyInfo.PropertyType; var settingDescriptionAttribute = propertyInfo.GetCustomAttribute(); string? settingDescription = settingDescriptionAttribute?.Description; var previewAttribute = propertyInfo.GetCustomAttribute(); TextBlock? previewLabel = null; Func? getPreview = null; if (previewAttribute != null) { var method = previewAttribute.PreviewClass.GetMethod(previewAttribute.PreviewFunction, BindingFlags.Public | BindingFlags.Static); if (method != null) { getPreview = value => (string)method.Invoke(null, new[] { value })!; previewLabel = new TextBlock { Width = 600, Height = 30, Padding = new Thickness(15, 0, 0, 0), FontStyle = FontStyle.Italic, FontSize = 13, Text = getPreview(initialValue) }; } } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { return CreateListControl(name, description, type, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview); } else if (type == typeof(string) && propertyInfo.GetCustomAttribute() != null) { return CreateFileControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview, propertyInfo.GetCustomAttribute()!.Filter); } else if (type == typeof(string) && name.EndsWith("Path", StringComparison.OrdinalIgnoreCase)) { return CreatePathControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview); } else if (type == typeof(string)) { return CreateStringControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview); } else if (type == typeof(int)) { return CreateIntControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview); } else if (type == typeof(bool)) { return CreateBoolControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview); } else if (type.IsEnum) { return CreateEnumControl(description, type, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview); } else { return new TextBlock { Text = $"Unsupported type: {type.Name}" }; } } private Control CreateListControl(string name, string description, Type listType, object initialValue, Action valueChangedCallback, OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var elementType = listType.GetGenericArguments()[0]; var list = (IList)initialValue ?? (IList)Activator.CreateInstance(listType)!; var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; var textBox = new TextBox { Text = GetListDisplayText(list), Width = 500, Height = 60, IsReadOnly = true, AcceptsReturn = true, TextWrapping = TextWrapping.Wrap }; var editButton = new Button { Content = "Edit" }; editButton.Click += async (s, e) => { var listEditor = new ListEditorDialog(elementType, list, this, optionsWindow, name); var result = await listEditor.ShowDialog(optionsWindow); if (result != null) { list.Clear(); foreach (object o in result) { list.Add(o); } textBox.Text = GetListDisplayText(list); valueChangedCallback(list); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(list); } }; return CreateOuterPanel(settingDescription, previewLabel, label, textBox, editButton); } private string GetListDisplayText(IList list) { if (list == null || list.Count == 0) return "(empty list)"; var items = new List(); foreach (var item in list) { items.Add(item?.ToString() ?? "(null)"); } return string.Join(Environment.NewLine, items); } private Control CreatePathControl(string name, string description, object initialValue, Action valueChangedCallback, OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var textBox = new TextBox { Text = initialValue as string, Width = 400 }; var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; var browseButton = new Button { Content = "Browse" }; browseButton.Click += async (s, e) => { var storageProvider = optionsWindow.StorageProvider; var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { AllowMultiple = false, Title = "Select Folder" }); if (result.Count > 0) { var path = result[0].Path.LocalPath; textBox.Text = path; valueChangedCallback(path); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(path); } }; textBox.TextChanged += (s, e) => { valueChangedCallback(textBox.Text ?? ""); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(textBox.Text ?? ""); }; var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; row.Children.Add(textBox); row.Children.Add(browseButton); return CreateOuterPanel(settingDescription, previewLabel, label, row); } private Control CreateFileControl(string name, string description, object initialValue, Action valueChangedCallback, OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func? getPreview, string filter) { var textBox = new TextBox { Text = initialValue as string, Width = 400 }; var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; var browseButton = new Button { Content = "Browse" }; browseButton.Click += async (s, e) => { var storageProvider = optionsWindow.StorageProvider; var result = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = false, Title = "Select File", FileTypeFilter = new[] { new FilePickerFileType(filter) { Patterns = new[] { filter } } } }); if (result.Count > 0) { var path = result[0].Path.LocalPath; textBox.Text = path; valueChangedCallback(path); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(path); } }; textBox.TextChanged += (s, e) => { valueChangedCallback(textBox.Text ?? ""); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(textBox.Text ?? ""); }; var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; row.Children.Add(textBox); row.Children.Add(browseButton); return CreateOuterPanel(settingDescription, previewLabel, label, row); } private Control CreateStringControl(string description, object initialValue, Action valueChangedCallback, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var textBox = new TextBox { Text = initialValue as string, Width = 600 }; var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; textBox.TextChanged += (s, e) => { valueChangedCallback(textBox.Text ?? ""); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(textBox.Text ?? ""); }; return CreateOuterPanel(settingDescription, previewLabel, label, textBox); } private Control CreateIntControl(string description, object initialValue, Action valueChangedCallback, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; var numericUpDown = new NumericUpDown { Minimum = 0, Maximum = 99999999999, Value = Convert.ToDecimal(initialValue), Width = 200, FormatString = "0" }; numericUpDown.ValueChanged += (s, e) => { valueChangedCallback(Convert.ToInt32(numericUpDown.Value)); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(Convert.ToInt32(numericUpDown.Value)); }; return CreateOuterPanel(settingDescription, previewLabel, label, numericUpDown); } private Control CreateBoolControl(string description, object initialValue, Action valueChangedCallback, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var checkBox = new CheckBox { Content = description, IsChecked = (bool)initialValue }; checkBox.IsCheckedChanged += (s, e) => { var isChecked = checkBox.IsChecked == true; valueChangedCallback(isChecked); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(isChecked); }; return CreateOuterPanel(settingDescription, previewLabel, checkBox); } private Control CreateEnumControl(string description, Type enumType, object initialValue, Action valueChangedCallback, string? settingDescription, TextBlock? previewLabel, Func? getPreview) { var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) }; var comboBox = new ComboBox { Width = 200 }; var enumValues = Enum.GetValues(enumType); foreach (var value in enumValues) { comboBox.Items.Add(value); } comboBox.SelectedItem = initialValue; comboBox.SelectionChanged += (s, e) => { if (comboBox.SelectedItem != null) { valueChangedCallback(comboBox.SelectedItem); if (previewLabel != null && getPreview != null) previewLabel.Text = getPreview(comboBox.SelectedItem); } }; return CreateOuterPanel(settingDescription, previewLabel, label, comboBox); } private Control CreateOuterPanel(string? settingDescription, TextBlock? previewLabel, params Control[] controls) { var outerPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4, Margin = new Thickness(0, 25, 0, 0), HorizontalAlignment = HorizontalAlignment.Left }; if (!string.IsNullOrEmpty(settingDescription)) { var descriptionLabel = new TextBlock { Text = settingDescription, FontStyle = FontStyle.Italic, Padding = new Thickness(0, 5, 0, 0) }; outerPanel.Children.Add(descriptionLabel); } foreach (var control in controls) { outerPanel.Children.Add(control); } if (previewLabel != null) outerPanel.Children.Add(previewLabel); return outerPanel; } }