Skip to content

Commit e91a1db

Browse files
committed
support customed display order
1 parent a2c2b53 commit e91a1db

8 files changed

Lines changed: 137 additions & 8 deletions

File tree

Demo/AutoSettingUI.Ursa.Demo/Models/ApplicationSettings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,17 @@ public class NetworkSettings
262262
public int Port { get; set; } = 8080;
263263

264264
[Title("Use HTTPS")]
265+
[DisplayOrder(-1)]
265266
public bool UseHttps { get; set; } = false;
266267

267268
[SubHeader("Authentication")]
268269
[Title("Username")]
270+
[DisplayOrder(1)]
269271
public string Username { get; set; } = "";
270272

271273
[Title("Timeout (seconds)")]
272274
[Range(1, 300)]
275+
[DisplayOrder(1)]
273276
public int Timeout { get; set; } = 30;
274277

275278
[SubHeader("Actions")]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ public class ThemeSettings
311311
| `[Placeholder]` | Property | Placeholder text for input |
312312
| `[Layout]` | Property | Custom layout (width, height) |
313313
| `[Validation]` | Property | Custom validation method |
314+
| `[DisplayOrder]` | Property | Controls display order (lower = first) |
314315

315316
## Custom Control Binding
316317

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ public class ThemeSettings
237237
| `[Placeholder]` | 属性 | 输入框占位文本 |
238238
| `[Layout]` | 属性 | 自定义布局(宽度、高度) |
239239
| `[Validation]` | 属性 | 自定义验证方法 |
240+
| `[DisplayOrder]` | 属性 | 控制显示顺序(数值小在前)|
240241

241242
## 自定义控件绑定
242243

manual/attributes.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,35 @@ Specifies a custom control type to use for rendering this specific property.
147147
[ControlBinding(typeof(ColorPicker), BindingProperty = "Color")]
148148
public Color AccentColor { get; set; }
149149
```
150+
151+
---
152+
153+
## `[DisplayOrder]`
154+
155+
Controls the display order of properties within a settings class. Properties with lower order values are displayed first. Properties with the same order value are displayed in their declaration order.
156+
157+
**Target:** `Property`
158+
159+
| Parameter | Type | Default | Description |
160+
| -------------- | ----- | ------- | ---------------------------------------------- |
161+
| `order` (ctor) | `int` | `0` | Display order value. Lower values appear first. |
162+
163+
```csharp
164+
[SettingUI]
165+
public class AppSettings
166+
{
167+
// Displayed third (order = 2)
168+
[DisplayOrder(2)]
169+
[Title("Advanced Options")]
170+
public bool EnableAdvanced { get; set; }
171+
172+
// Displayed first (order = 0, default)
173+
[Title("Application Name")]
174+
public string AppName { get; set; }
175+
176+
// Displayed second (order = 1)
177+
[DisplayOrder(1)]
178+
[Title("Version")]
179+
public string Version { get; set; }
180+
}
181+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace AutoSettingUI.Core.Attributes;
2+
3+
/// <summary>
4+
/// Specifies the display order of a property in the settings UI.
5+
/// Properties with lower order values are displayed first.
6+
/// Properties with the same order value are displayed in their declaration order.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
9+
public sealed class DisplayOrderAttribute : Attribute
10+
{
11+
/// <summary>
12+
/// Gets the display order value.
13+
/// Lower values are displayed first.
14+
/// Default is 0.
15+
/// </summary>
16+
public int Order { get; }
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="DisplayOrderAttribute"/> class.
20+
/// </summary>
21+
/// <param name="order">The display order value. Lower values are displayed first.</param>
22+
public DisplayOrderAttribute(int order)
23+
{
24+
Order = order;
25+
}
26+
}

src/AutoSettingUI.Core/Models/PropertyDescriptor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ public sealed class PropertyDescriptor
184184
/// </summary>
185185
public double NumericIncrement { get; }
186186

187+
/// <summary>
188+
/// Gets the display order of the property.
189+
/// Lower values are displayed first.
190+
/// Properties with the same order are displayed in their declaration order.
191+
/// </summary>
192+
public int DisplayOrder { get; }
193+
187194
/// <summary>
188195
/// Initializes a new instance of the <see cref="PropertyDescriptor"/> class.
189196
/// </summary>
@@ -221,7 +228,8 @@ public PropertyDescriptor(
221228
bool isNumericUpDown = false,
222229
double numericMinimum = 0,
223230
double numericMaximum = 0,
224-
double numericIncrement = 0)
231+
double numericIncrement = 0,
232+
int displayOrder = 0)
225233
{
226234
PropertyName = propertyName;
227235
DisplayName = displayName;
@@ -257,6 +265,7 @@ public PropertyDescriptor(
257265
NumericMinimum = numericMinimum;
258266
NumericMaximum = numericMaximum;
259267
NumericIncrement = numericIncrement;
268+
DisplayOrder = displayOrder;
260269
}
261270
}
262271

src/AutoSettingUI.Core/Providers/ReflectionSettingDescriptorProvider.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,26 @@ public bool HasDescriptor(string typeName)
8282
List<PropertyDescriptor>? currentSubProps = null;
8383
string? currentSubHeader = null;
8484

85-
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
85+
// Get all properties with their declaration order
86+
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
87+
.Select((prop, index) => new { Property = prop, DeclarationOrder = index })
88+
.ToList();
89+
90+
// Sort by DisplayOrder, then by declaration order
91+
var sortedProps = props
92+
.Select(p => new
93+
{
94+
p.Property,
95+
p.DeclarationOrder,
96+
DisplayOrder = p.Property.GetCustomAttribute<DisplayOrderAttribute>()?.Order ?? 0
97+
})
98+
.OrderBy(p => p.DisplayOrder)
99+
.ThenBy(p => p.DeclarationOrder)
100+
.ToList();
101+
102+
foreach (var item in sortedProps)
86103
{
104+
var prop = item.Property;
87105
// Skip hidden properties
88106
if (prop.GetCustomAttribute<HideAttribute>() is not null)
89107
continue;
@@ -111,6 +129,7 @@ public bool HasDescriptor(string typeName)
111129
var placeholderAttr = prop.GetCustomAttribute<PlaceholderAttribute>();
112130
var descriptionAttr = prop.GetCustomAttribute<DescriptionAttribute>();
113131
var passwordAttr = prop.GetCustomAttribute<PasswordAttribute>();
132+
var displayOrderAttr = prop.GetCustomAttribute<DisplayOrderAttribute>();
114133

115134
// Pre-compute enum values to avoid runtime Enum.GetValues in non-AOT path
116135
string[]? enumValues = null;
@@ -166,7 +185,8 @@ public bool HasDescriptor(string typeName)
166185
false,
167186
0,
168187
0,
169-
0
188+
0,
189+
displayOrderAttr?.Order ?? 0
170190
);
171191

172192
if (currentSubProps is not null)

src/AutoSettingUI.Generator/Incremental/AutoSettingGenerator.cs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class AutoSettingGenerator : IIncrementalGenerator
3333
private const string PasswordAttributeName = "PasswordAttribute";
3434
private const string NumericUpDownAttributeName = "NumericUpDownAttribute"; // Special handling needed - type depends on property type
3535
private const string ControlBindingDefaultsAttributeName = "ControlBindingDefaultsAttribute";
36+
private const string DisplayOrderAttributeName = "DisplayOrderAttribute";
3637

3738
// Diagnostic descriptors
3839
private static readonly DiagnosticDescriptor NonPublicClassWarning = new(
@@ -333,8 +334,17 @@ private static void AppendRegisterMethod(StringBuilder sb, INamedTypeSymbol cls,
333334
sb.AppendLine(" string? currentSubTitle = null;");
334335
sb.AppendLine();
335336

336-
foreach (var prop in GetPublicInstanceProperties(cls, context))
337+
var allProps = GetPublicInstanceProperties(cls, context).ToList();
338+
var propsWithOrder = allProps.Select((prop, index) => new
337339
{
340+
Property = prop,
341+
DisplayOrder = GetDisplayOrder(prop),
342+
DeclarationOrder = prop.DeclarationOrder
343+
}).ToList();
344+
345+
foreach (var item in propsWithOrder.OrderBy(p => p.DisplayOrder).ThenBy(p => p.DeclarationOrder))
346+
{
347+
var prop = item.Property;
338348
if (GetAttr(prop, HideAttributeName) != null) continue;
339349

340350
var subHeaderAttr = GetAttr(prop, SubHeaderAttributeName);
@@ -514,6 +524,15 @@ private static void AppendRegisterMethod(StringBuilder sb, INamedTypeSymbol cls,
514524
numericInc = TryGetNamedArgDouble(cbAttr, "Increment") ?? 1.0;
515525
}
516526

527+
// DisplayOrder
528+
var displayOrderAttr = GetAttr(prop, DisplayOrderAttributeName);
529+
int displayOrder = 0;
530+
if (displayOrderAttr != null && displayOrderAttr.ConstructorArguments.Length > 0)
531+
{
532+
if (displayOrderAttr.ConstructorArguments[0].Value is int orderVal)
533+
displayOrder = orderVal;
534+
}
535+
517536
// CollectionEditor
518537
var collEditorAttr = GetAttr(prop, CollectionEditorAttributeName);
519538
string? collEditorTypeName = null;
@@ -615,7 +634,8 @@ private static void AppendRegisterMethod(StringBuilder sb, INamedTypeSymbol cls,
615634
sb.AppendLine($" {(isNumericUpDown ? "true" : "false")},");
616635
sb.AppendLine($" {numericMin},");
617636
sb.AppendLine($" {numericMax},");
618-
sb.AppendLine($" {numericInc});");
637+
sb.AppendLine($" {numericInc},");
638+
sb.AppendLine($" {displayOrder});");
619639

620640
var varName = $"pd_{safeName}_{EscapeName(prop.Name)}";
621641
sb.AppendLine($" if (currentSub != null) currentSub.Add({varName}); else directProps.Add({varName});");
@@ -676,6 +696,7 @@ private sealed class PropertyInfo
676696
public Location? Location { get; }
677697
public bool IsFromObservableProperty { get; }
678698
public IFieldSymbol? SourceField { get; }
699+
public int DeclarationOrder { get; }
679700

680701
public PropertyInfo(
681702
string name,
@@ -685,7 +706,8 @@ public PropertyInfo(
685706
ImmutableArray<AttributeData> attributes,
686707
Location? location,
687708
bool isFromObservableProperty,
688-
IFieldSymbol? sourceField)
709+
IFieldSymbol? sourceField,
710+
int declarationOrder = 0)
689711
{
690712
Name = name;
691713
Type = type;
@@ -695,11 +717,13 @@ public PropertyInfo(
695717
Location = location;
696718
IsFromObservableProperty = isFromObservableProperty;
697719
SourceField = sourceField;
720+
DeclarationOrder = declarationOrder;
698721
}
699722
}
700723

701724
private static IEnumerable<PropertyInfo> GetPublicInstanceProperties(INamedTypeSymbol cls, SourceProductionContext context)
702725
{
726+
int order = 0;
703727
var properties = cls.GetMembers()
704728
.OfType<IPropertySymbol>()
705729
.Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsStatic)
@@ -711,7 +735,8 @@ private static IEnumerable<PropertyInfo> GetPublicInstanceProperties(INamedTypeS
711735
p.GetAttributes(),
712736
p.Locations.FirstOrDefault(),
713737
false,
714-
null));
738+
null,
739+
order++));
715740

716741
var allFields = cls.GetMembers().OfType<IFieldSymbol>().ToList();
717742
var observableFields = new List<PropertyInfo>();
@@ -728,7 +753,8 @@ private static IEnumerable<PropertyInfo> GetPublicInstanceProperties(INamedTypeS
728753
f.GetAttributes(),
729754
f.Locations.FirstOrDefault(),
730755
true,
731-
f));
756+
f,
757+
order++));
732758
}
733759
}
734760

@@ -845,6 +871,17 @@ private static string GetPropertyNameFromField(string fieldName)
845871
});
846872
}
847873

874+
private static int GetDisplayOrder(PropertyInfo prop)
875+
{
876+
var attr = GetAttr(prop, DisplayOrderAttributeName);
877+
if (attr != null && attr.ConstructorArguments.Length > 0)
878+
{
879+
if (attr.ConstructorArguments[0].Value is int orderVal)
880+
return orderVal;
881+
}
882+
return 0;
883+
}
884+
848885
private static AttributeData? GetAttrInherited(ISymbol symbol, string baseName)
849886
{
850887
return symbol.GetAttributes().FirstOrDefault(a =>

0 commit comments

Comments
 (0)