Skip to content

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

Closed
kekekeks opened this issue May 7, 2019 · 36 comments
Closed

Alternative markup syntax proposal. #2502

kekekeks opened this issue May 7, 2019 · 36 comments
Labels

Comments

@kekekeks
Copy link
Member

kekekeks commented May 7, 2019

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:

// Default namespaces for Avalonia, x: and d: are omitted here and added automatically
using local = clr MyNamespace
using lib = xml http://some.lib/xml/namespace
Window 
{
     StackPanel
     {
          // Equivalent of <Button>
          Button
          // Unless followed by = before the end of line, in this case it's a property assignment
          Orientation = "Horizontal"
          Attached.Property="Value"
          // This is an equivalent of <Button Background="Red">Click me</Button>
          Button
          {
               "Click me"
               Background = "Red"
          }
          local:MyControl
          lib:LibraryControl
          clr:System.Collections.Generic.List<clr:System.Int32> {
                1
                2
                3 4 5 6
          }
          // Markup extensions are prefixed by $
          TextBlock { Text = ${Binding SomeVmProp} }
          // $-prefixed strings are a shortcut to {Binding ...}
          TextBlock { x:Name = "Name" Text = $"SomeVmProp" }
          TextBlock { Text = $"#Name.Text, Mode=TwoWay" }
          TextBlock {
                  "Plain text"
                  Run { Background="Red" "Text" FontFamily="Tahoma" }
                  "Plain text"
          }
     }
     Styles = 
     {
          // Styles property is treated in specific way, all root elements are considered to be <Style Selector="{LINE}"> to reduce boilerplate
          StackPanel > is:(Control).someClass:pointerover
          {
                Margin = "0 0 1 4"
          }
          Button
          {
                Template =
                {
                     // ControlTemplate is omitted here and is assumed by default. We can probably do the same for regular XAML
                     ContentPresenter
                     {
                           Name = "PART_ContentPresenter"
                           Background = ${TemplateBinding Background}
                           TextBlock.Foreground = ${TemplateBinding Foreground}
                     }
                }
          }
     }
}

XAML for comparison:

<Window 
   xmlns="https://github.com/avaloniaui"
   xmlns:local="clr-namespace:MyNamespace"
   xmlns:lib="http://some.lib/xml/namespace"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:scg="clr-namespace:System.Collections.Generic;assembly=netstandard">
   xmlns:sys="clr-namespace:System;assembly=netstandard">
  <StackPanel 
     Orientation="Horizontal" Attached.Property="Value">
     <Button>
     <Button Background="Red">
        Click me
     </Button>
     <local:MyControl/>
     <lib:LibraryControl/>
     <scg:List x:TypeArguments="sys:Int32">
       <x:Int32>1</x:Int32>
       <x:Int32>2</x:Int32>
       <x:Int32>3</x:Int32>
       <x:Int32>4</x:Int32>
       <x:Int32>5</x:Int32>
       <x:Int32>6</x:Int32>
     </scg:List>
     <TextBlock Text="{Binding SomeVmProp}"/>
     <TextBlock x:Name="Name" Text="{Binding SomeVmProp}"/>
     <TextBlock Text="{Binding #Name.Text, Mode=TwoWay}"/>
     <TextBlock>
        Plain text
        <Run Background="Red" FontFamily="Tahoma">Text</Run>
        Plain text
     </TextBlock>
   </StackPanel>
   <Window.Styles>
      <Style Selector="StackPanel > is:(Control).someClass:pointerover">
          <Setter Property="Margin" Value="0 0 1 4"/>
      </Style>
      <Style Selector="Button">
         <Setter Property="Template">
            <ControlTemplate>
               <ContentPresenter 
                    Name="PART_ContentPresenter"
                    Background = "{TemplateBinding Background}"
                    TextBlock.Foreground = "{TemplateBinding Foreground}"/>
            </ControlTemplate>
         </Setter>
      </Style>
    </Window.Styles>
</Window>
@grokys
Copy link
Member

grokys commented May 7, 2019

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.

@wieslawsoltes
Copy link
Collaborator

wieslawsoltes commented May 7, 2019

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")}" }
    }
}

@kekekeks
Copy link
Member Author

kekekeks commented May 7, 2019

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:
image

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

  • export x:Name-ed objects as C# fields
  • declare functions to be used by bindings as converters
  • use C# code for event handlers, like
<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.

@kekekeks
Copy link
Member Author

kekekeks commented May 7, 2019

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.

@x2bool
Copy link

x2bool commented May 8, 2019

Look at what Google just announced today at I/O 2019: https://developer.android.com/jetpack/compose

Demo:
https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/ButtonDemo.kt

But that probably only looks good with Kotlin syntax though :( Sadly C# does not have much support for DSLs.

@wieslawsoltes
Copy link
Collaborator

wieslawsoltes commented May 8, 2019

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" }
    }
}

@wieslawsoltes
Copy link
Collaborator

wieslawsoltes commented May 8, 2019

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} }
        }
    }
}

@wieslawsoltes
Copy link
Collaborator

wieslawsoltes commented May 8, 2019

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}

@kekekeks
Copy link
Member Author

kekekeks commented May 8, 2019

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.

@JaggerJo
Copy link
Contributor

JaggerJo commented May 8, 2019

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.

https://elm-lang.org/blog/blazing-fast-html

@worldbeater
Copy link
Contributor

worldbeater commented May 9, 2019

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.

@JaggerJo
Copy link
Contributor

JaggerJo commented May 9, 2019

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"
    })
}

@kekekeks
Copy link
Member Author

@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.

@JaggerJo
Copy link
Contributor

@kekekeks ahh, missed the XAMLIL part in the description. Thought this is a general "alternative markup syntax" discussion.

@TonyHenrique
Copy link

I prefer the XAML syntax.

@ronnyek
Copy link

ronnyek commented Jun 14, 2019

I actually really like the idea of something like what was described here:
#2502 (comment)

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 }

@moloneymb
Copy link

@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.

@JaggerJo
Copy link
Contributor

JaggerJo commented Jul 15, 2019

@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...

@kekekeks
Copy link
Member Author

kekekeks commented Jul 15, 2019

@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.

@moloneymb
Copy link

@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.

@ahopper
Copy link
Contributor

ahopper commented Jul 22, 2019

Here is another functional ui https://azul.rs/

@handicraftsman
Copy link

handicraftsman commented Oct 17, 2019

There are such things as SDLang. Flutter-like approach like above would be nice too though, for both C# and F#.

@snikeguo
Copy link

snikeguo commented Dec 20, 2019

@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"
}

@handicraftsman
Copy link

Implementing QML as-is in Avalonia should be hard enough if i understand correctly

@handicraftsman
Copy link

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.

@kekekeks
Copy link
Member Author

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.

@kekekeks
Copy link
Member Author

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

@snikeguo
Copy link

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)

image

@robloo
Copy link
Contributor

robloo commented May 21, 2020

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)
https://youtu.be/TeS4rX3i_Ls?t=3374

Yes, I'm seeding this all over the place because it's awesome!

image

@jkoritzinsky
Copy link
Collaborator

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.

@byme8
Copy link

byme8 commented Jul 25, 2021

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/

@warappa
Copy link

warappa commented Sep 23, 2023

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.

Highlights

  • Hot reload works flawlessly
  • IClassicDesktopStyleApplicationLifetime support
  • Controls are mapped
  • Generator to build Blazor bindings
    • With option to fine-tune property handling
  • Bindings works
    • Basic bindings
    • Panels
    • Data templates
    • Control templates (partially)

Todo

  • Test ISingleViewApplicationLifetime
  • Navigation view
  • Markup text handling
  • Support all control templates

The code is still messy and some controls are missing/not hand-tuned, but it works pretty well already.

Sample App Preview

The sample Hello World app looks like this:
image

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!

@Simplifier
Copy link

It looks cool. Is threre any progress on this proposal?

@Tamnac
Copy link
Contributor

Tamnac commented Dec 15, 2023

It looks cool. Is threre any progress on this proposal?

Check the repo he linked. There's some mild activity

@Simplifier
Copy link

Simplifier commented Dec 16, 2023

Check the repo he linked. There's some mild activity

Who linked the repo? kekekeks? I can't find any links

@Tamnac
Copy link
Contributor

Tamnac commented Dec 17, 2023

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

@AvaloniaUI AvaloniaUI locked and limited conversation to collaborators Jan 17, 2024
@maxkatz6 maxkatz6 converted this issue into discussion #14255 Jan 17, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Projects
None yet
Development

No branches or pull requests