AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.96k stars 2.25k forks source link

Alternative markup syntax proposal. #2502

Closed kekekeks closed 10 months ago

kekekeks commented 5 years ago

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 commented 5 years 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.

wieslawsoltes commented 5 years ago

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 commented 5 years ago

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

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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

TonyHenrique commented 5 years ago

I prefer the XAML syntax.

ronnyek commented 5 years ago

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 }
moloneymb commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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

handicraftsman commented 5 years ago

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

snikeguo commented 4 years ago

@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 commented 4 years ago

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

handicraftsman commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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!

image

jkoritzinsky commented 3 years ago

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 commented 3 years ago

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 commented 1 year ago

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

Todo

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 commented 11 months ago

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

Tamnac commented 11 months ago

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

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

Simplifier commented 11 months ago

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

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

Tamnac commented 11 months ago

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