Initial better gui implemented.

UX and information still missing alot
This commit is contained in:
Frederik Jacobsen 2025-10-18 18:09:17 +02:00
parent 66f6347f4a
commit 813e1ce6e9
5 changed files with 136 additions and 13 deletions

View File

@ -0,0 +1,24 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using XSDVisualiser.Models;
namespace XSDVisualiser.Desktop.Converters
{
public class CardinalityToLabelConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Occurs occ)
{
var min = occ.Min;
var max = occ.MaxIsUnbounded ? "*" : occ.Max.ToString(culture);
return $"[{min}..{max}]";
}
return null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using XSDVisualiser.Models;
namespace XSDVisualiser.Desktop.Converters
{
public class OptionalToBorderBrushConverter : IValueConverter
{
private static readonly IBrush RequiredBrush = new SolidColorBrush(Color.FromRgb(0x33, 0x99, 0x33));
private static readonly IBrush OptionalBrush = new SolidColorBrush(Color.FromRgb(0xCC, 0x66, 0x33));
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Occurs occ)
{
// Optional if minOccurs == 0
return occ.Min == 0 ? OptionalBrush : RequiredBrush;
}
return RequiredBrush;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
using XSDVisualiser.Models;
namespace XSDVisualiser.Desktop.Converters
{
public class OptionalToThicknessConverter : IValueConverter
{
private static readonly Thickness Required = new Thickness(2);
private static readonly Thickness Optional = new Thickness(2);
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Occurs occ)
{
// We could vary thickness; for now same thickness regardless, reserved for future styling.
return occ.Min == 0 ? Optional : Required;
}
return Required;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}

View File

@ -1,10 +1,59 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:XSDVisualiser.Models;assembly=XSDVisualiser.Core"
xmlns:conv="clr-namespace:XSDVisualiser.Desktop.Converters"
x:Class="XSDVisualiser.Desktop.MainWindow" x:Class="XSDVisualiser.Desktop.MainWindow"
Title="XSD Visualiser" Width="1000" Height="700"> x:DataType="m:SchemaModel"
<StackPanel Margin="12" Spacing="8"> Title="XSD Visualiser" Width="1100" Height="800">
<TextBlock Text="XSD Visualiser (Avalonia)" FontSize="18"/> <Window.Resources>
<Button Content="Open XSD and parse" x:Name="OpenBtn" Width="200"/> <conv:CardinalityToLabelConverter x:Key="CardinalityToLabel"/>
<TextBox x:Name="Output" AcceptsReturn="True" Height="600" TextWrapping="Wrap" FontFamily="Consolas"/> <conv:OptionalToBorderBrushConverter x:Key="OptionalToBrush"/>
</StackPanel> <conv:OptionalToThicknessConverter x:Key="OptionalToThickness"/>
</Window.Resources>
<Grid RowDefinitions="Auto,*" Margin="12">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="XSD Visualiser (Avalonia)" FontSize="18"/>
<Button Content="Open XSD and parse" x:Name="OpenBtn" Width="200" Margin="12,0,0,0"/>
</StackPanel>
<ScrollViewer Grid.Row="1">
<TreeView x:Name="SchemaTree" ItemsSource="{Binding RootElements}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type m:SchemaNode}" ItemsSource="{Binding Children}">
<StackPanel>
<!-- Connector line from parent to this node (hidden for roots where ContentModel is null) -->
<Grid Margin="0,6,0,2" IsVisible="{Binding ContentModel, Converter={x:Static ObjectConverters.IsNotNull}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- leading small elbow -->
<Rectangle Grid.Column="0" Width="16" Height="2" Fill="#888" VerticalAlignment="Center"/>
<Grid Grid.Column="1">
<Rectangle Height="2" Fill="#888" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Cardinality, Converter={StaticResource CardinalityToLabel}}"
Background="#F0F0F0" Padding="4,0"
HorizontalAlignment="Center"/>
</Grid>
</Grid>
<!-- Node content -->
<Border CornerRadius="4"
BorderBrush="{Binding Cardinality, Converter={StaticResource OptionalToBrush}}"
BorderThickness="{Binding Cardinality, Converter={StaticResource OptionalToThickness}}"
Padding="8" Margin="0,0,0,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding TypeName}" Foreground="#555"/>
<TextBlock Text="{Binding BuiltInType}" Foreground="#777"/>
<TextBlock Text="{Binding ContentModel}" Foreground="#999"/>
</StackPanel>
</Border>
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
</ScrollViewer>
</Grid>
</Window> </Window>

View File

@ -1,24 +1,21 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using XSDVisualiser.Models;
using XSDVisualiser.Parsing; using XSDVisualiser.Parsing;
using XSDVisualiser.Utils;
namespace XSDVisualiser.Desktop namespace XSDVisualiser.Desktop
{ {
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private Button _openBtn = null!; private Button _openBtn = null!;
private TextBox _output = null!;
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
_openBtn = this.FindControl<Button>("OpenBtn"); _openBtn = this.FindControl<Button>("OpenBtn");
_output = this.FindControl<TextBox>("Output");
_openBtn.Click += OpenBtn_Click; _openBtn.Click += OpenBtn_Click;
} }
@ -45,12 +42,11 @@ namespace XSDVisualiser.Desktop
{ {
var parser = new XsdSchemaParser(); var parser = new XsdSchemaParser();
var model = parser.Parse(path); var model = parser.Parse(path);
var text = Serialization.ToJson(model); Dispatcher.UIThread.Post(() => DataContext = model);
Dispatcher.UIThread.Post(() => _output.Text = text);
} }
catch (Exception ex) catch (Exception ex)
{ {
Dispatcher.UIThread.Post(() => _output.Text = ex.ToString()); Dispatcher.UIThread.Post(() => DataContext = new SchemaModel { TargetNamespace = ex.Message });
} }
}); });
} }