using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace XFEWarframe.Desktop.Editing;
/// <summary>Inline setting cards grouped by purpose. Searching never replaces fields or their drafts.</summary>
public sealed class GroupedSettingsForm : StackPanel
{
public IReadOnlyList<EditorField>? Fields
{
get => (IReadOnlyList<EditorField>?)GetValue(FieldsProperty);
set => SetValue(FieldsProperty, value);
}
public static readonly DependencyProperty FieldsProperty = DependencyProperty.Register(nameof(Fields), typeof(IReadOnlyList<EditorField>), typeof(GroupedSettingsForm), new PropertyMetadata(null, (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
public string Scope { get => (string)GetValue(ScopeProperty); set => SetValue(ScopeProperty, value); }
public static readonly DependencyProperty ScopeProperty = DependencyProperty.Register(nameof(Scope), typeof(string), typeof(GroupedSettingsForm), new PropertyMetadata("account", (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
public string Query { get => (string)GetValue(QueryProperty); set => SetValue(QueryProperty, value); }
public static readonly DependencyProperty QueryProperty = DependencyProperty.Register(nameof(Query), typeof(string), typeof(GroupedSettingsForm), new PropertyMetadata("", (sender, _) => ((GroupedSettingsForm)sender).Filter()));
public bool EnableSearch { get => (bool)GetValue(EnableSearchProperty); set => SetValue(EnableSearchProperty, value); }
public static readonly DependencyProperty EnableSearchProperty = DependencyProperty.Register(nameof(EnableSearch), typeof(bool), typeof(GroupedSettingsForm), new PropertyMetadata(true, (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
private sealed record Row(EditorField Field, SettingsCard Card);
private sealed record Section(SettingSection Definition, SettingsExpander Expander, Row[] Rows);
private readonly List<Section> sections = [];
private readonly Dictionary<string, bool> expansion = [];
private AutoSuggestBox? search;
private readonly TextBlock empty = new() { Text = UiText.Translate("没有匹配的设置"), Opacity = .7, Margin = new Thickness(12) };
private bool filtering;
public GroupedSettingsForm() => Spacing = 8;
private void Rebuild()
{
Children.Clear(); sections.Clear(); search = null;
if (EnableSearch && Fields?.Count >= 8)
{
search = new AutoSuggestBox { DisplayMemberPath = nameof(EditorField.Label), Text = Query, PlaceholderText = UiText.Translate("搜索设置"), Margin = new Thickness(0, 0, 0, 4) };
Suggestions.SetCandidates(search, Fields);
search.TextChanged += (sender, _) => { if (ReferenceEquals(sender, search)) Query = search.Text; };
Children.Add(search);
}
foreach (var group in (Fields ?? []).GroupBy(x => SettingSections.Find(Scope, x.Key)).OrderBy(x => x.Key.Order))
{
var id = Scope + ":" + group.Key.Key;
var expander = new SettingsExpander
{
Header = UiText.Translate(group.Key.Title), Tag = group.Key.Key,
IsExpanded = expansion.GetValueOrDefault(id, group.Key.Order == 0),
HorizontalAlignment = HorizontalAlignment.Stretch
};
AutomationProperties.SetAutomationId(expander, "settings-group-" + group.Key.Key);
// Save only the user's normal browsing state, not automatic search expansion.
expander.RegisterPropertyChangedCallback(SettingsExpander.IsExpandedProperty, (_, _) =>
{ if (!filtering && string.IsNullOrWhiteSpace(Query)) expansion[id] = expander.IsExpanded; });
var fields = group.ToArray();
var cards = EditorForm.CreateCards(fields);
foreach (var card in cards) expander.Items.Add(card);
sections.Add(new(group.Key, expander, fields.Zip(cards, (field, card) => new Row(field, card)).ToArray()));
Children.Add(expander);
}
Children.Add(empty); Filter();
}
private void Filter()
{
filtering = true;
try
{
var query = Query.Trim();
if (search is not null && search.Text != Query) search.Text = Query;
var any = false;
foreach (var section in sections)
{
var groupMatches = UiText.Translate(section.Definition.Title).Contains(query, StringComparison.OrdinalIgnoreCase);
var count = 0;
foreach (var row in section.Rows)
{
var matches = groupMatches || row.Field.Label.Contains(query, StringComparison.OrdinalIgnoreCase) || row.Field.Key.Contains(query, StringComparison.OrdinalIgnoreCase);
row.Card.Visibility = matches ? Visibility.Visible : Visibility.Collapsed;
if (matches) count++;
}
section.Expander.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed;
section.Expander.Description = UiText.Format("{0} 项设置", count);
section.Expander.IsExpanded = query.Length > 0 || expansion.GetValueOrDefault(Scope + ":" + section.Definition.Key, section.Definition.Order == 0);
any |= count > 0;
}
empty.Visibility = any ? Visibility.Collapsed : Visibility.Visible;
}
finally { filtering = false; }
}
}
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace XFEWarframe.Desktop.Editing;
/// <summary>Inline setting cards grouped by purpose. Searching never replaces fields or their drafts.</summary>
public sealed class GroupedSettingsForm : StackPanel
{
public IReadOnlyList<EditorField>? Fields
{
get => (IReadOnlyList<EditorField>?)GetValue(FieldsProperty);
set => SetValue(FieldsProperty, value);
}
public static readonly DependencyProperty FieldsProperty = DependencyProperty.Register(nameof(Fields), typeof(IReadOnlyList<EditorField>), typeof(GroupedSettingsForm), new PropertyMetadata(null, (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
public string Scope { get => (string)GetValue(ScopeProperty); set => SetValue(ScopeProperty, value); }
public static readonly DependencyProperty ScopeProperty = DependencyProperty.Register(nameof(Scope), typeof(string), typeof(GroupedSettingsForm), new PropertyMetadata("account", (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
public string Query { get => (string)GetValue(QueryProperty); set => SetValue(QueryProperty, value); }
public static readonly DependencyProperty QueryProperty = DependencyProperty.Register(nameof(Query), typeof(string), typeof(GroupedSettingsForm), new PropertyMetadata("", (sender, _) => ((GroupedSettingsForm)sender).Filter()));
public bool EnableSearch { get => (bool)GetValue(EnableSearchProperty); set => SetValue(EnableSearchProperty, value); }
public static readonly DependencyProperty EnableSearchProperty = DependencyProperty.Register(nameof(EnableSearch), typeof(bool), typeof(GroupedSettingsForm), new PropertyMetadata(true, (sender, _) => ((GroupedSettingsForm)sender).Rebuild()));
private sealed record Row(EditorField Field, SettingsCard Card);
private sealed record Section(SettingSection Definition, SettingsExpander Expander, Row[] Rows);
private readonly List<Section> sections = [];
private readonly Dictionary<string, bool> expansion = [];
private AutoSuggestBox? search;
private readonly TextBlock empty = new() { Text = UiText.Translate("没有匹配的设置"), Opacity = .7, Margin = new Thickness(12) };
private bool filtering;
public GroupedSettingsForm() => Spacing = 8;
private void Rebuild()
{
Children.Clear(); sections.Clear(); search = null;
if (EnableSearch && Fields?.Count >= 8)
{
search = new AutoSuggestBox { DisplayMemberPath = nameof(EditorField.Label), Text = Query, PlaceholderText = UiText.Translate("搜索设置"), Margin = new Thickness(0, 0, 0, 4) };
Suggestions.SetCandidates(search, Fields);
search.TextChanged += (sender, _) => { if (ReferenceEquals(sender, search)) Query = search.Text; };
Children.Add(search);
}
foreach (var group in (Fields ?? []).GroupBy(x => SettingSections.Find(Scope, x.Key)).OrderBy(x => x.Key.Order))
{
var id = Scope + ":" + group.Key.Key;
var expander = new SettingsExpander
{
Header = UiText.Translate(group.Key.Title), Tag = group.Key.Key,
IsExpanded = expansion.GetValueOrDefault(id, group.Key.Order == 0),
HorizontalAlignment = HorizontalAlignment.Stretch
};
AutomationProperties.SetAutomationId(expander, "settings-group-" + group.Key.Key);
// Save only the user's normal browsing state, not automatic search expansion.
expander.RegisterPropertyChangedCallback(SettingsExpander.IsExpandedProperty, (_, _) =>
{ if (!filtering && string.IsNullOrWhiteSpace(Query)) expansion[id] = expander.IsExpanded; });
var fields = group.ToArray();
var cards = EditorForm.CreateCards(fields);
foreach (var card in cards) expander.Items.Add(card);
sections.Add(new(group.Key, expander, fields.Zip(cards, (field, card) => new Row(field, card)).ToArray()));
Children.Add(expander);
}
Children.Add(empty); Filter();
}
private void Filter()
{
filtering = true;
try
{
var query = Query.Trim();
if (search is not null && search.Text != Query) search.Text = Query;
var any = false;
foreach (var section in sections)
{
var groupMatches = UiText.Translate(section.Definition.Title).Contains(query, StringComparison.OrdinalIgnoreCase);
var count = 0;
foreach (var row in section.Rows)
{
var matches = groupMatches || row.Field.Label.Contains(query, StringComparison.OrdinalIgnoreCase) || row.Field.Key.Contains(query, StringComparison.OrdinalIgnoreCase);
row.Card.Visibility = matches ? Visibility.Visible : Visibility.Collapsed;
if (matches) count++;
}
section.Expander.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed;
section.Expander.Description = UiText.Format("{0} 项设置", count);
section.Expander.IsExpanded = query.Length > 0 || expansion.GetValueOrDefault(Scope + ":" + section.Definition.Key, section.Definition.Order == 0);
any |= count > 0;
}
empty.Visibility = any ? Visibility.Collapsed : Visibility.Visible;
}
finally { filtering = false; }
}
}