-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative markup syntax proposal. #2502
Comments
Nice, very similar to a syntax I have been thinking about, based somewhat on QML. Shouldn't be a pre-1.0 priority IMO but would definitely be nice to explore. One suggestion: what about handlebar syntax for bindings: Also: including C# in markup would be a KILLER feature. |
Proposal for C# syntax embedded inside xaml file: Current syntax (C# and XAML file separate): public class IsToolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ITool tool && parameter is string name)
{
if (tool.Title == name)
{
return true;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
} <MenuItem Header="Scr_ibble" Command="{Binding SetTool}" CommandParameter="Scribble">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="{Binding CurrentTool, Converter={StaticResource IsToolConverter}, ConverterParameter=Scribble, Mode=OneWay}"/>
</MenuItem.Icon>
</MenuItem> alternative syntax (single XAML file): csharp {
using Draw2D.ViewModels;
bool IsTool(ITool value, string name) => tool?.Title == name;
}
MenuItem {
Header="Scr_ibble", Command="{Binding SetTool}", CommandParameter="Scribble"
Icon {
CheckBox { BorderThickness="0" IsHitTestVisible="False" IsChecked="{x:Bind IsTool(CurrentTool, "Scribble")}" }
}
} |
Design-time support would be a bit wonky since compiled code would be in a separate assembly where we can't disable visibility checks. We can inject |
Look at what Google just announced today at I/O 2019: https://developer.android.com/jetpack/compose But that probably only looks good with Kotlin syntax though :( Sadly C# does not have much support for DSLs. |
Playing with the alternative syntax using some of my existing xaml. XAML: <Grid RowDefinitions="Auto,*" ColumnDefinitions="*">
<Border x:Name="button" Background="{DynamicResource GreenBrush}" Width="100" Height="50" Grid.Row="1" Grid.Column="0" Margin="5,0,0,5" HorizontalAlignment="Center" VerticalAlignment="Center">
<i:Interaction.Behaviors>
<ia:EventTriggerBehavior EventName="PointerPressed" SourceObject="{Binding #button}">
<iac:CaptureMouseDeviceAction/>
<ia:ChangePropertyAction TargetObject="{Binding #button}" PropertyName="Background" Value="{DynamicResource RedBrush}"/>
<ia:ChangePropertyAction TargetObject="{Binding #text}" PropertyName="Foreground" Value="{DynamicResource YellowBrush}"/>
<ia:CallMethodAction TargetObject="{Binding}" MethodName="IncrementCount"/>
</ia:EventTriggerBehavior>
<ia:EventTriggerBehavior EventName="PointerReleased" SourceObject="{Binding #button}">
<iac:ReleaseMouseDeviceAction/>
<ia:ChangePropertyAction TargetObject="{Binding #button}" PropertyName="Background" Value="{DynamicResource GreenBrush}"/>
<ia:ChangePropertyAction TargetObject="{Binding #text}" PropertyName="Foreground" Value="{DynamicResource WhiteBrush}"/>
<ia:CallMethodAction TargetObject="{Binding}" MethodName="DecrementCount"/>
</ia:EventTriggerBehavior>
</i:Interaction.Behaviors>
<TextBlock x:Name="text" Text="{Binding Count}" Foreground="{DynamicResource WhiteBrush}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</Grid> Alternative markup syntax: Grid { RowDefinitions="Auto,*", ColumnDefinitions="*"
Border { x:Name="button", Background=${DynamicResource GreenBrush}, Width="100", Height="50", Grid.Row="1", Grid.Column="0", Margin="5,0,0,5", HorizontalAlignment="Center", VerticalAlignment="Center"
i:Interaction.Behaviors {
ia:EventTriggerBehavior { EventName="PointerPressed", SourceObject=${Binding #button}
Actions {
iac:CaptureMouseDeviceAction
ia:ChangePropertyAction { TargetObject=${Binding #button}, PropertyName="Background", Value=${DynamicResource RedBrush} }
ia:ChangePropertyAction { TargetObject=${Binding #text}, PropertyName="Foreground", Value=${DynamicResource YellowBrush} }
ia:CallMethodAction { TargetObject=${Binding}, MethodName="IncrementCount" }
}
}
ia:EventTriggerBehavior { EventName="PointerReleased", SourceObject=${Binding #button}
Actions {
iac:ReleaseMouseDeviceAction
ia:ChangePropertyAction { TargetObject=${Binding #button}, PropertyName="Background", Value=${DynamicResource GreenBrush} }
ia:ChangePropertyAction { TargetObject=${Binding #text}, PropertyName="Foreground", Value=${DynamicResource WhiteBrush} }
ia:CallMethodAction { TargetObject="{Binding}", MethodName="DecrementCount" }
}
}
TextBlock { x:Name="text", Text=${Binding Count}, Foreground=${DynamicResource WhiteBrush}, VerticalAlignment="Center", HorizontalAlignment="Center" }
}
} |
Another example: XAML: <UserControl x:Class="Draw2D.Views.Style.ShapeStyleView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Draw2D.Views.Style"
mc:Ignorable="d"
d:DesignWidth="400" d:DesignHeight="300">
<TabControl Classes="default" TabStripPlacement="Top">
<TabItem Classes="default" Header="Style">
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,*">
<TextBlock Classes="default" Grid.Column="0" Grid.Row="0" Text="Thickness"/>
<TextBox Classes="default" Grid.Column="1" Grid.Row="0" Text="{Binding Thickness}"/>
<TextBlock Classes="default" Grid.Column="0" Grid.Row="1" Text="IsStroked"/>
<CheckBox Classes="default" Grid.Column="1" Grid.Row="1" IsChecked="{Binding IsStroked}"/>
<TextBlock Classes="default" Grid.Column="0" Grid.Row="2" Text="IsFilled"/>
<CheckBox Classes="default" Grid.Column="1" Grid.Row="2" IsChecked="{Binding IsFilled}"/>
</Grid>
</TabItem>
<TabItem Classes="default" Header="Stroke">
<ContentControl Content="{Binding Stroke}"/>
</TabItem>
<TabItem Classes="default" Header="Fill">
<ContentControl Content="{Binding Fill}"/>
</TabItem>
<TabItem Classes="default" Header="Text">
<ContentControl Content="{Binding TextStyle}"/>
</TabItem>
</TabControl>
</UserControl> Alternative markup syntax: using local = clr Draw2D.Views.Style
UserControl {
d:DesignWidth="400"
d:DesignHeight="300"
TabControl {
Classes="default", TabStripPlacement="Top"
TabItem {
Classes="default", Header="Style"
Grid {
RowDefinitions="Auto,Auto,Auto", ColumnDefinitions="Auto,*"
TextBlock { Classes="default", Grid.Column="0", Grid.Row="0", Text="Thickness" }
TextBox { Classes="default", Grid.Column="1", Grid.Row="0", Text=${Binding Thickness} }
TextBlock { Classes="default", Grid.Column="0", Grid.Row="1", Text="IsStroked" }
CheckBox { Classes="default", Grid.Column="1", Grid.Row="1", IsChecked=${Binding IsStroked} }
TextBlock { Classes="default", Grid.Column="0", Grid.Row="2", Text="IsFilled"/>
CheckBox { Classes="default", Grid.Column="1", Grid.Row="2", IsChecked=${Binding IsFilled} }
}
}
TabItem {
Classes="default", Header="Stroke"
ContentControl { Content=${Binding Stroke} }
}
TabItem {
Classes="default" Header="Fill">
ContentControl { Content=${Binding Fill} }
}
TabItem {
Classes="default" Header="Text">
ContentControl { Content=${Binding TextStyle} }
}
}
} |
or maybe something like this: using local = clr Draw2D.Views.Style
UserControl
d:DesignWidth="400"
d:DesignHeight="300"
TabControl
Classes="default", TabStripPlacement="Top"
TabItem
Classes="default", Header="Style"
Grid
RowDefinitions="Auto,Auto,Auto", ColumnDefinitions="Auto,*"
TextBlock => Classes="default", Grid.Column="0", Grid.Row="0", Text="Thickness"
TextBox => Classes="default", Grid.Column="1", Grid.Row="0", Text=${Binding Thickness}
TextBlock => Classes="default", Grid.Column="0", Grid.Row="1", Text="IsStroked"
CheckBox => Classes="default", Grid.Column="1", Grid.Row="1", IsChecked=${Binding IsStroked}
TextBlock => Classes="default", Grid.Column="0", Grid.Row="2", Text="IsFilled"
CheckBox => Classes="default", Grid.Column="1", Grid.Row="2", IsChecked=${Binding IsFilled}
TabItem
Classes="default", Header="Stroke"
ContentControl => Content=${Binding Stroke}
TabItem
Classes="default" Header="Fill">
ContentControl => Content=${Binding Fill}
TabItem {
Classes="default" Header="Text">
ContentControl => Content=${Binding TextStyle} |
I've noticed a bit of confusion with
I'm not sure how to unify this syntax. Quoting strings seems more natural, but it might be inconvenient for binding paths. I think we can make quotes optional and only use them when they are actually needed, e. g.
but
That would make object-typed properties a bit more verbose, e. g.
instead of
but I guees it's justified to make everything else to be way less verbose. |
It would be interesting to have an abstract model of the UI for MVU/React/Elm/Flutter like approaches. You can play with and edit some MVU samples online (Elm /F#) // Called each time AppState changes
IView View(AppState state)
{
new StackPanel
{
Orientation = Orientation.Vertical,
// Because this is just C#/F#.. we can just filter inline
Children = state.Data
.Where(data => state.filter data)
.Select(data => ViewData(data))
}
}
IView View(Data state)
{
new StackPanel
{
Orientation = Orientation.Horizontal,
Children = [
new TextBlock
{
Text = Data.Title
},
new Button
{
Click = code // dispatch changes to app state -> rerenders by calling view
}
]
}
} I guess diffing with the current VisualTree would be needed for performance. |
Wow, curious concept, looks great! By the way, folks who don't like XAML syntax usually say it's too verbose, so probably worth using indentation (spaces, tabs, etc.) as logical block delimiters, like tools such as Pug or Elm do. How about something like the following elm-like example? using lib = xml "http://some.lib/xml/namespace"
using x = xml "http://schemas.microsoft.com/winfx/2006/xaml"
using collections = clr System.Collections.Generic
using local = clr Full.Path.To.Namespace
using system = clr System
Window
StackPanel
Button
Orientation = "Horizontal"
Attached.Property = "Value"
Command = {Binding ViewModel.Clicked}
"Hello, Avalonia!"
-- A control with List<int> as its content.
local:MyControl
collections:List<system:Int32> { 1 2 3 }
-- Bound controls example.
TextBox x:Name = "ExampleTextBlock"
TextBlock Text = {Binding Text, Mode=OneWay, Source=ExampleTextBlock}
TextBlock
"Example text"
LineBreak
Run Background = "Red" "Red Text!"
LineBreak
"More plain text in the end"
Styles
StackPanel > is:(Control).someClass:pointerover
Margin = "0 0 1 4"
Button
Template
ContentPresenter
Name = "PART_ContentPresenter"
Background = {TemplateBinding Background}
TextBlock.Foreground = {TemplateBinding Foreground} It'd feel much nicer if an IDE highlighted nesting depth, like IntelliJ IDEA does. |
The cool thing about elm/f# in this regard is that you can build great DSLs with them, all in code. I'm actually playing around with building a DSL for Avalonia called FuncUI (WIP). This is valid F# code let buttonView = button {
background Brushes.Azure
foreground Brushes.Brown
contentView (textblock {
text "some text"
})
} |
@JaggerJo The problem with DSLs is the lack of live code reloading. With XAML and any alternative markup that get parsed into the same AST we can just reload the code at runtime which makes previewer useful. While it would be really nice to have something React-like, it would be a different project, not something we can implement with XamlIl. |
@kekekeks ahh, missed the XAMLIL part in the description. Thought this is a general "alternative markup syntax" discussion. |
I prefer the XAML syntax. |
I actually really like the idea of something like what was described here: With a pug like syntax. Its simple, concise. Honestly, I'd be happy with anything less verbose than xaml. Would things like the binding syntax change as well? I have always really been impressed with the sorts of things you can build with WPF, but found like it was always more of a chore to work with than any other UI frameworks I've played with. (mfc, winforms, wpf, qt, qtquick, android etc) I always thought it was convenient to define UI's like qt quick does, and still have them turn out being attractive etc.
|
@kekekeks @JaggerJo I write pure F# & WPF. See https://gist.github.com/moloneymb/107c24ca3705e72b672ff19290c9b3a7 for a complete custom control example. I think it should be theoretically possible to do a Unity3D style reflection based loading. It's something I'd like to build but don't have the time for atm. |
Cool! I created Avalonia.FuncUI for my own needs, and it works well. It uses a combination of compile time type constraints and reflection. https://github.com/JaggerJo/Avalonia.FuncUI EDIT: It’s fun that a while ago I experimented with it and now its a tool I rely on that just works. Also great to see other people joining in and contributing. It really feels like standing on the shoulders of giants... |
@moloneymb I'm not sure that DSLs are previewer friendly. With XAML we have the CLR control type mostly untouched and have the markup builder delegate in a static field, so it can be easily replaced in previewer mode, so everything that expects to see that particular control type will continue to work. I could add plugin support to our previewer engine, so one could extend it with whatever markup syntax, if anyone wants to experiment with previewer support for functional DSLs. |
@kekekeks would love to build such a plugin but there is no way I'll be able to find the time to do a good job at it. |
Here is another functional ui https://azul.rs/ |
There are such things as SDLang. Flutter-like approach like above would be nice too though, for both C# and F#. |
@kekekeks QML binding : |
Implementing QML as-is in Avalonia should be hard enough if i understand correctly |
It would be interesting to implement nice support for object initializing clause https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-initialize-objects-by-using-an-object-initializer Based on this it should be relatively easy to implement custom AOT DSLs for Avalonia, or to not use them at all. |
We won't depend on 3rd party JS engine, everything has to be done in managed code with as few dependencies as there possibly can be. Not sure how Qt does that, they might be somehow recording getter calls like MobX does. |
What we could do is some custom language suitable for defining expressions. Such expressions could be compiled into a MultiBinding with a customized value converter |
The "Blazor" syntax is a really cool concept too. microsoft/microsoft-ui-xaml#2499 "Proposal: Support Blazor Syntax" The link to the video where some ideas were presented today by the Uno Platform is below (time 56:14) Yes, I'm seeding this all over the place because it's awesome! |
Another option here would be to use a syntax based on KDL. It's XML-like but still much cleaner. We could build a flavor on top of it that would give a UX similar to the example in the OP on this issue. |
There is such thing like AmmyUI. It is JSON like language that are compiled to XAML. There was a sick hot reload implementation(current WPF/Xamarin not even close) and bunch of improvements like mixings, aliases, variables. I was experimenting with it ages ago and it was very promising. But looks like it is dead, but syntax was pretty good. Here is a demo: https://player.vimeo.com/video/198873582 |
Hi @robloo, hi @maxkatz6, You can find my fork at https://github.com/warappa/BlazorBindings.Maui/tree/avalonia-11. There is a simple HelloWorld project that you can run and play with. Highlights
Todo
The code is still messy and some controls are missing/not hand-tuned, but it works pretty well already. Sample App PreviewThe sample Hello World app looks like this: Code:@using BlazorBindings.AvaloniaBindings.Elements
@using System.Collections.ObjectModel;
@using BlazorBindings.AvaloniaBindings.Elements.Shapes
@using Microsoft.AspNetCore.Components.Rendering;
@using global::Avalonia.Layout
@using global::Avalonia
@using global::Avalonia.Media;
@using global::Avalonia.Media.Imaging;
<Window Topmost="true" Width="600" Height="500">
<StackPanel HorizontalAlignment="HorizontalAlignment.Center"
VerticalAlignment="VerticalAlignment.Center">
<TextBlock Text="Hello World"></TextBlock>
@if (buttonVisible == true)
{
<Button OnClick="OnCounterClicked">Click me</Button>
}
<TextBlock Text="@ButtonText"></TextBlock>
<CheckBox @bind-IsChecked="buttonVisible">Button visible</CheckBox>
<ListBox ItemsSource="Items">
<ItemTemplate>
<StackPanel>
<TextBlock Text="Content:"></TextBlock>
<TextBlock Text="@context"></TextBlock>
</StackPanel>
</ItemTemplate>
<ItemsPanel>
<StackPanel Orientation="Orientation.Horizontal"></StackPanel>
</ItemsPanel>
</ListBox>
<StackPanel Orientation="Orientation.Horizontal" Margin="new Thickness(0,20,0,0)">
<TextBlock Text="Ellipse size: "></TextBlock>
<TextBlock Text="@slider.ToString()"></TextBlock>
</StackPanel>
<Slider @bind-Value="slider"></Slider>
<Ellipse Width="slider" Height="slider/2" Fill="Brushes.Red"></Ellipse>
</StackPanel>
</Window>
@code {
double slider = 50;
int count = 0;
bool? buttonVisible { get; set; } = true;
ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>(new[]
{
"a",
"b",
"c"
});
void ToggleButton()
{
buttonVisible = !buttonVisible;
}
string ButtonText => count switch
{
0 => "Click me",
1 => $"Clicked 1 time",
_ => $"Clicked {count} times"
};
void OnCounterClicked()
{
count++;
}
protected override void OnInitialized()
{
base.OnInitialized();
}
} Please check it out! |
It looks cool. Is threre any progress on this proposal? |
Check the repo he linked. There's some mild activity |
Who linked the repo? kekekeks? I can't find any links |
@warappa in his comment about blazor bindings. I believe that's the only active proposal at the moment |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Since the very first thing XamlIl does is conversion of XML to its own AST, we can now create other markup languages relatively cheaply by simply writing a parser that produces the same AST.
Since some people seem to hate XML, here is a non-XML markup proposal:
XAML for comparison:
The text was updated successfully, but these errors were encountered: