Skip to content

Commit 2dbb3d8

Browse files
authored
优化layout (#11)
1 parent 4a2f6f9 commit 2dbb3d8

6 files changed

Lines changed: 296 additions & 39 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public string Email
162162
[Description("Your display name (3-20 characters)")]
163163
[Placeholder("Enter username")]
164164
[Validation(Required = true, MinLength = 3, MaxLength = 20, ErrorMessage = "Username must be 3-20 characters")]
165-
[Layout(Width = 200, Height = 28, Margin = "0,2,0,2")]
165+
[Layout(Width = 200, Height = 58, Margin = "0,2,0,2")]
166166
public string Username { get; set; } = "";
167167

168168
// Password: Custom mask character
@@ -342,6 +342,7 @@ public partial class ExtendedControlsSettings : ObservableObject
342342
[Title("Settings.SelectionTags", UseResourceKey = true)]
343343
[TagInput]
344344
public ObservableCollection<string> ProjectTags { get; set; } = ["Ursa", "Avalonia", "AutoSettingUI"];
345+
345346

346347
[Title("Settings.ReleaseDate", UseResourceKey = true)]
347348
[DatePicker]

src/AutoSettingUI.Core/Models/PropertyDescriptor.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,62 @@ public sealed class PropertyDescriptor
221221
/// </summary>
222222
public int DisplayOrder { get; }
223223

224+
#region Layout Properties
225+
226+
/// <summary>
227+
/// Gets the width for the control. double.NaN means Auto.
228+
/// </summary>
229+
public double LayoutWidth { get; }
230+
231+
/// <summary>
232+
/// Gets the height for the control. double.NaN means Auto.
233+
/// </summary>
234+
public double LayoutHeight { get; }
235+
236+
/// <summary>
237+
/// Gets the minimum width for the control.
238+
/// </summary>
239+
public double LayoutMinWidth { get; }
240+
241+
/// <summary>
242+
/// Gets the minimum height for the control.
243+
/// </summary>
244+
public double LayoutMinHeight { get; }
245+
246+
/// <summary>
247+
/// Gets the maximum width for the control.
248+
/// </summary>
249+
public double LayoutMaxWidth { get; }
250+
251+
/// <summary>
252+
/// Gets the maximum height for the control.
253+
/// </summary>
254+
public double LayoutMaxHeight { get; }
255+
256+
/// <summary>
257+
/// Gets the horizontal alignment for the control.
258+
/// Values: "Left", "Center", "Right", "Stretch"
259+
/// </summary>
260+
public string? LayoutHorizontalAlignment { get; }
261+
262+
/// <summary>
263+
/// Gets the vertical alignment for the control.
264+
/// Values: "Top", "Center", "Bottom", "Stretch"
265+
/// </summary>
266+
public string? LayoutVerticalAlignment { get; }
267+
268+
/// <summary>
269+
/// Gets the margin for the control.
270+
/// </summary>
271+
public string? LayoutMargin { get; }
272+
273+
/// <summary>
274+
/// Gets the padding for the control.
275+
/// </summary>
276+
public string? LayoutPadding { get; }
277+
278+
#endregion
279+
224280
/// <summary>
225281
/// Initializes a new instance of the <see cref="PropertyDescriptor"/> class.
226282
/// </summary>
@@ -262,7 +318,17 @@ public PropertyDescriptor(
262318
int displayOrder = 0,
263319
string? displayNameKey = null,
264320
string? placeholderKey = null,
265-
string? descriptionKey = null)
321+
string? descriptionKey = null,
322+
double layoutWidth = double.NaN,
323+
double layoutHeight = double.NaN,
324+
double layoutMinWidth = double.NaN,
325+
double layoutMinHeight = double.NaN,
326+
double layoutMaxWidth = double.NaN,
327+
double layoutMaxHeight = double.NaN,
328+
string? layoutHorizontalAlignment = null,
329+
string? layoutVerticalAlignment = null,
330+
string? layoutMargin = null,
331+
string? layoutPadding = null)
266332
{
267333
PropertyName = propertyName;
268334
DisplayName = displayName;
@@ -302,6 +368,16 @@ public PropertyDescriptor(
302368
NumericMaximum = numericMaximum;
303369
NumericIncrement = numericIncrement;
304370
DisplayOrder = displayOrder;
371+
LayoutWidth = layoutWidth;
372+
LayoutHeight = layoutHeight;
373+
LayoutMinWidth = layoutMinWidth;
374+
LayoutMinHeight = layoutMinHeight;
375+
LayoutMaxWidth = layoutMaxWidth;
376+
LayoutMaxHeight = layoutMaxHeight;
377+
LayoutHorizontalAlignment = layoutHorizontalAlignment;
378+
LayoutVerticalAlignment = layoutVerticalAlignment;
379+
LayoutMargin = layoutMargin;
380+
LayoutPadding = layoutPadding;
305381
}
306382
}
307383

src/AutoSettingUI.Core/Providers/ReflectionSettingDescriptorProvider.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public bool HasDescriptor(string typeName)
132132
var descriptionAttr = prop.GetCustomAttribute<DescriptionAttribute>();
133133
var passwordAttr = prop.GetCustomAttribute<PasswordAttribute>();
134134
var displayOrderAttr = prop.GetCustomAttribute<DisplayOrderAttribute>();
135+
var layoutAttr = prop.GetCustomAttribute<LayoutAttribute>();
135136

136137
// Pre-compute enum values to avoid runtime Enum.GetValues in non-AOT path
137138
string[]? enumValues = null;
@@ -191,7 +192,17 @@ public bool HasDescriptor(string typeName)
191192
displayOrderAttr?.Order ?? 0,
192193
titleAttr?.UseResourceKey == true ? titleAttr.Name : null,
193194
placeholderAttr?.UseResourceKey == true ? placeholderAttr.Text : null,
194-
descriptionAttr?.UseResourceKey == true ? descriptionAttr.Text : (titleAttr?.UseDescriptionKey == true ? titleAttr.Description : null)
195+
descriptionAttr?.UseResourceKey == true ? descriptionAttr.Text : (titleAttr?.UseDescriptionKey == true ? titleAttr.Description : null),
196+
layoutAttr?.Width ?? double.NaN,
197+
layoutAttr?.Height ?? double.NaN,
198+
layoutAttr?.MinWidth ?? double.NaN,
199+
layoutAttr?.MinHeight ?? double.NaN,
200+
layoutAttr?.MaxWidth ?? double.NaN,
201+
layoutAttr?.MaxHeight ?? double.NaN,
202+
layoutAttr?.HorizontalAlignment,
203+
layoutAttr?.VerticalAlignment,
204+
layoutAttr?.Margin,
205+
layoutAttr?.Padding
195206
);
196207

197208
if (currentSubProps is not null)

src/AutoSettingUI.Generator/Incremental/AutoSettingGenerator.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class AutoSettingGenerator : IIncrementalGenerator
3434
private const string NumericUpDownAttributeName = "NumericUpDownAttribute"; // Special handling needed - type depends on property type
3535
private const string ControlBindingDefaultsAttributeName = "ControlBindingDefaultsAttribute";
3636
private const string DisplayOrderAttributeName = "DisplayOrderAttribute";
37+
private const string LayoutAttributeName = "LayoutAttribute";
3738

3839
// Diagnostic descriptors
3940
private static readonly DiagnosticDescriptor NonPublicClassWarning = new(
@@ -546,6 +547,32 @@ private static void AppendRegisterMethod(StringBuilder sb, INamedTypeSymbol cls,
546547
displayOrder = orderVal;
547548
}
548549

550+
// Layout
551+
var layoutAttr = GetAttr(prop, LayoutAttributeName);
552+
double layoutWidth = double.NaN;
553+
double layoutHeight = double.NaN;
554+
double layoutMinWidth = double.NaN;
555+
double layoutMinHeight = double.NaN;
556+
double layoutMaxWidth = double.NaN;
557+
double layoutMaxHeight = double.NaN;
558+
string? layoutHAlign = null;
559+
string? layoutVAlign = null;
560+
string? layoutMargin = null;
561+
string? layoutPadding = null;
562+
if (layoutAttr != null)
563+
{
564+
layoutWidth = TryGetNamedArgDouble(layoutAttr, "Width") ?? double.NaN;
565+
layoutHeight = TryGetNamedArgDouble(layoutAttr, "Height") ?? double.NaN;
566+
layoutMinWidth = TryGetNamedArgDouble(layoutAttr, "MinWidth") ?? double.NaN;
567+
layoutMinHeight = TryGetNamedArgDouble(layoutAttr, "MinHeight") ?? double.NaN;
568+
layoutMaxWidth = TryGetNamedArgDouble(layoutAttr, "MaxWidth") ?? double.NaN;
569+
layoutMaxHeight = TryGetNamedArgDouble(layoutAttr, "MaxHeight") ?? double.NaN;
570+
layoutHAlign = GetNamedArgString(layoutAttr, "HorizontalAlignment");
571+
layoutVAlign = GetNamedArgString(layoutAttr, "VerticalAlignment");
572+
layoutMargin = GetNamedArgString(layoutAttr, "Margin");
573+
layoutPadding = GetNamedArgString(layoutAttr, "Padding");
574+
}
575+
549576
// CollectionEditor
550577
var collEditorAttr = GetAttr(prop, CollectionEditorAttributeName);
551578
string? collEditorTypeName = null;
@@ -651,7 +678,17 @@ private static void AppendRegisterMethod(StringBuilder sb, INamedTypeSymbol cls,
651678
sb.AppendLine($" {displayOrder},");
652679
sb.AppendLine($" {displayNameKey},");
653680
sb.AppendLine($" {placeholderKey},");
654-
sb.AppendLine($" {descriptionKey});");
681+
sb.AppendLine($" {descriptionKey},");
682+
sb.AppendLine($" {FormatDouble(layoutWidth)},");
683+
sb.AppendLine($" {FormatDouble(layoutHeight)},");
684+
sb.AppendLine($" {FormatDouble(layoutMinWidth)},");
685+
sb.AppendLine($" {FormatDouble(layoutMinHeight)},");
686+
sb.AppendLine($" {FormatDouble(layoutMaxWidth)},");
687+
sb.AppendLine($" {FormatDouble(layoutMaxHeight)},");
688+
sb.AppendLine($" {layoutHAlign ?? "null"},");
689+
sb.AppendLine($" {layoutVAlign ?? "null"},");
690+
sb.AppendLine($" {layoutMargin ?? "null"},");
691+
sb.AppendLine($" {layoutPadding ?? "null"});");
655692

656693
var varName = $"pd_{safeName}_{EscapeName(prop.Name)}";
657694
sb.AppendLine($" if (currentSub != null) currentSub.Add({varName}); else directProps.Add({varName});");
@@ -976,6 +1013,14 @@ private static int GetDisplayOrder(PropertyInfo prop)
9761013
return null;
9771014
}
9781015

1016+
private static string FormatDouble(double value)
1017+
{
1018+
if (double.IsNaN(value)) return "double.NaN";
1019+
if (double.IsPositiveInfinity(value)) return "double.PositiveInfinity";
1020+
if (double.IsNegativeInfinity(value)) return "double.NegativeInfinity";
1021+
return value.ToString(System.Globalization.CultureInfo.InvariantCulture);
1022+
}
1023+
9791024
/// <summary>Gets a named argument value as a Type symbol.</summary>
9801025
private static INamedTypeSymbol? GetNamedArgType(AttributeData? attr, string key)
9811026
{

src/Extensions/AutoSettingUI.Avalonia/Controls/AvaloniaAutoSettingPanel.cs

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -993,34 +993,92 @@ private bool CanExecuteDelegate(Core.Models.PropertyDescriptor prop, object targ
993993
/// </summary>
994994
private void ApplyControlAttributes(global::Avalonia.Controls.Control control, Core.Models.PropertyDescriptor prop, object target)
995995
{
996-
var propertyInfo = GetPropertyInfo(target, prop.PropertyName);
997-
if (propertyInfo != null)
998-
{
999-
// Apply layout attributes
1000-
control.ApplyLayout(propertyInfo);
996+
// Apply layout from pre-generated PropertyDescriptor (AOT-safe)
997+
ApplyLayoutFromDescriptor(control, prop);
1001998

1002-
// Apply placeholder to TextBox
1003-
if (control is TextBox textBox)
1004-
{
1005-
textBox.ApplyPlaceholder(propertyInfo);
1006-
}
1007-
1008-
// Apply description (tooltip)
1009-
control.ApplyDescription(propertyInfo);
1010-
}
1011-
1012-
// AOT-safe fallback using generated metadata
1013-
if (control is TextBox tb && !string.IsNullOrEmpty(prop.PlaceholderText))
999+
// Apply placeholder to TextBox
1000+
if (control is TextBox textBox && !string.IsNullOrEmpty(prop.PlaceholderText))
10141001
{
1015-
tb.Watermark = prop.PlaceholderText;
1002+
textBox.Watermark = prop.PlaceholderText;
10161003
}
10171004

1005+
// Apply description (tooltip)
10181006
if (!string.IsNullOrEmpty(prop.DescriptionText))
10191007
{
10201008
ToolTip.SetTip(control, prop.DescriptionText);
10211009
}
10221010
}
10231011

1012+
/// <summary>
1013+
/// Applies layout properties from PropertyDescriptor (AOT-safe, no reflection).
1014+
/// </summary>
1015+
private static void ApplyLayoutFromDescriptor(global::Avalonia.Controls.Control control, Core.Models.PropertyDescriptor prop)
1016+
{
1017+
if (!double.IsNaN(prop.LayoutWidth)) control.Width = prop.LayoutWidth;
1018+
if (!double.IsNaN(prop.LayoutHeight)) control.Height = prop.LayoutHeight;
1019+
if (!double.IsNaN(prop.LayoutMinWidth)) control.MinWidth = prop.LayoutMinWidth;
1020+
if (!double.IsNaN(prop.LayoutMinHeight)) control.MinHeight = prop.LayoutMinHeight;
1021+
if (!double.IsNaN(prop.LayoutMaxWidth)) control.MaxWidth = prop.LayoutMaxWidth;
1022+
if (!double.IsNaN(prop.LayoutMaxHeight)) control.MaxHeight = prop.LayoutMaxHeight;
1023+
1024+
if (!string.IsNullOrEmpty(prop.LayoutHorizontalAlignment))
1025+
control.HorizontalAlignment = ParseHorizontalAlignment(prop.LayoutHorizontalAlignment);
1026+
1027+
if (!string.IsNullOrEmpty(prop.LayoutVerticalAlignment))
1028+
control.VerticalAlignment = ParseVerticalAlignment(prop.LayoutVerticalAlignment);
1029+
1030+
if (!string.IsNullOrEmpty(prop.LayoutMargin))
1031+
control.Margin = ParseThickness(prop.LayoutMargin);
1032+
1033+
if (!string.IsNullOrEmpty(prop.LayoutPadding))
1034+
{
1035+
var padding = ParseThickness(prop.LayoutPadding);
1036+
if (control is ContentControl contentControl)
1037+
contentControl.Padding = padding;
1038+
else if (control is Decorator decorator)
1039+
decorator.Padding = padding;
1040+
}
1041+
}
1042+
1043+
private static HorizontalAlignment ParseHorizontalAlignment(string? value)
1044+
=> value?.ToLowerInvariant() switch
1045+
{
1046+
"left" => HorizontalAlignment.Left,
1047+
"center" => HorizontalAlignment.Center,
1048+
"right" => HorizontalAlignment.Right,
1049+
"stretch" => HorizontalAlignment.Stretch,
1050+
_ => HorizontalAlignment.Stretch
1051+
};
1052+
1053+
private static VerticalAlignment ParseVerticalAlignment(string? value)
1054+
=> value?.ToLowerInvariant() switch
1055+
{
1056+
"top" => VerticalAlignment.Top,
1057+
"center" => VerticalAlignment.Center,
1058+
"bottom" => VerticalAlignment.Bottom,
1059+
"stretch" => VerticalAlignment.Stretch,
1060+
_ => VerticalAlignment.Stretch
1061+
};
1062+
1063+
private static Thickness ParseThickness(string? value)
1064+
{
1065+
if (string.IsNullOrEmpty(value)) return new Thickness(0);
1066+
1067+
var parts = value.Split(',').Select(p => p.Trim()).ToArray();
1068+
1069+
if (parts.Length == 1 && double.TryParse(parts[0], out var uniform))
1070+
return new Thickness(uniform);
1071+
1072+
if (parts.Length == 4 &&
1073+
double.TryParse(parts[0], out var left) &&
1074+
double.TryParse(parts[1], out var top) &&
1075+
double.TryParse(parts[2], out var right) &&
1076+
double.TryParse(parts[3], out var bottom))
1077+
return new Thickness(left, top, right, bottom);
1078+
1079+
return new Thickness(0);
1080+
}
1081+
10241082
/// <summary>
10251083
/// Creates a TextBox with password support and validation.
10261084
/// </summary>

0 commit comments

Comments
 (0)