Closed kekekeks closed 10 months ago
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: {{binding}}
?
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")}" }
}
}
Also: including C# in markup would be a KILLER feature.
That can be actually done even with regular XAML via x:Code
directive like WF does:
The thing is that we can't do it in a seamless way since XAML "variables" in bindings aren't exactly C# variables, they are usually bindings instead.
What we can do seamlessly is
<TextBlock x:Name="Counter"/>
<x:Code>
<![CDATA[
int _counter;
]]>
</x:Code>
<Button>
<Button.OnClick>
<x:Code>
<![CDATA[
_counter++;
Counter.Text = _counter.ToString();
]]>
</x:Code>
</Button.OnClick>
</Button>
Which would generate something like this:
class InlineXamlClass
{
public TextBlock Counter;
#line 123 "/path/to/file.xaml"
int _counter;
#line default
public void EventHandler_7997bffc3b414e01b329106b084bb5a4(object sender, RoutedEventArgs args)
{
#line 130 "/path/to/file.xaml"
_counter++;
Counter.Text = _counter.ToString();
#line default
}
}
Such C# code will be compiled separately from the main assembly after it was compiled and then merged back via ILRepack. The type and method visibility checks will be disabled by feeding the second stage compiler with a tampered assembly with everything made public so it won't complain.
More complex integration like binding expressions written completely in C# would require us to analyze and rewrite C# AST.
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 [assembly:InternalsVisibleTo("AvaloniaXaml")]
, but that's it.
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 ,
, ""
and markup extensions:
1) in "normal" syntax ,
is not needed, but ""
are required for text
2) in markup extensions ,
is needed, but ""
are not
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.
d:DesignWidth=400
HorizontalAlignment=Vertical
Title=Test
but
// Quotes are needed because
Title="Some long title"
StackPanel
{
// Quotes are needed to treat "Text" as a string value instead of `Text` class
Button { "Text" }
"Text"
Button { "Other text" }
}
That would make object-typed properties a bit more verbose, e. g.
ItemsControl
{
ItemsPanelTemplate={Canvas}
}
instead of
Button
{
ItemsPanelTemplate=Canvas
}
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: https://github.com/AvaloniaUI/Avalonia/issues/2502#issuecomment-491047485
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.
Grid {
columns: 3
columnSpacing: 32
rowSpacing: 16
signal buttonPressed
Button { text: "7" }
Button { text: "8" }
Button { text: "9" }
Button { text: "4" }
Button { text: "5" }
Button { text: "6" }
Button { text: "1" }
Button { text: "2" }
Button { text: "3" }
Button { text: "0" }
Button { text: "."; dimmable: true }
Button { text: " " }
Button { text: "±"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "−"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "+"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "√"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "÷"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "×"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "C"; color: "#6da43d"; operator: true }
Button { text: " "; color: "#6da43d"; operator: true }
Button { text: "="; color: "#6da43d"; operator: true; dimmable: true }
@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.
@moloneymb
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 Until now, I haven't reached WPF binding. QML binding is very simple, just like the programmer's thinking. If you think JS is not good, I recommend using TypeScript
QML binding : https://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html QML does not have TemplateBinding, XXXXBinding etc. QML have JSengine QML link: TextBox { id:textbox1 } TextBox { text:textbox1.text+" hello world!"+"123456789" }
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. We could somewhat employ C# compiler to produce MSIL, but it will bring up another problem: using arbitrary code that is referencing arbitrary variables doesn't play well with granular updates. I. e. it's not possible to tell what property depends on what in this case. So we'll have to recompute everything which is bad for performance.
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
I think it is theoretically feasible to convert some scripting language (such as JS / TS) into XAML or MSIL
Qt commercial version has a tool: QML TO CPP, this tool allows QML to run in the MCU(such as stm32)
The "Blazor" syntax is a really cool concept too. https://github.com/microsoft/microsoft-ui-xaml/issues/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) https://youtu.be/TeS4rX3i_Ls?t=3374
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 And robust syntax description: http://www.ammyui.com/documentation/syntax/
Hi @robloo, hi @maxkatz6, I'm currently working on Blazor bindings based on BlazorBindings.Maui.
You can find my fork at https://github.com/warappa/BlazorBindings.Maui/tree/avalonia-11.
Be sure to check out the avalonia-11
branch.
There is a simple HelloWorld project that you can run and play with.
IClassicDesktopStyleApplicationLifetime
supportISingleViewApplicationLifetime
The code is still messy and some controls are missing/not hand-tuned, but it works pretty well already.
The sample Hello World app looks like this:
@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?
It looks cool. Is threre any progress on this proposal?
Check the repo he linked. There's some mild activity
Check the repo he linked. There's some mild activity
Who linked the repo? kekekeks? I can't find any links
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
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: