Skip to content
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

use css as styling file #3686

Closed
furesoft opened this issue Mar 21, 2020 · 22 comments
Closed

use css as styling file #3686

furesoft opened this issue Mar 21, 2020 · 22 comments

Comments

@furesoft
Copy link

how can i use css files instead of xaml files?

@wdcossey
Copy link
Contributor

I don't think there will ever be support for loading css file.

I am not sure what you want to accomplish but Avalonia uses XAML markup language.

https://avaloniaui.net/docs/

@danwalmsley
Copy link
Member

Actually at some point there was some project that brought CSS to Avalonia, iv not seen it for a long time though now.

There is currently no plans for Avalonia to change to a different dialect. However if the community wants to build this kind of thing its a great idea.

@sgf
Copy link

sgf commented Dec 10, 2021

https://github.com/warappa/XamlCSS

@sgf
Copy link

sgf commented Dec 10, 2021

I don't think there will ever be support for loading css file.

I am not sure what you want to accomplish but Avalonia uses XAML markup language.

https://avaloniaui.net/docs/

In the design of Style, the design of XAML is very bad. It is highly redundant and difficult to read.

I suggest adding support for CSS-3 directly. Use various advanced improvements made to CSS styles in the web .

@sgf sgf mentioned this issue Dec 10, 2021
@maxkatz6
Copy link
Member

maxkatz6 commented Dec 10, 2021

@sgf from your another comment:

the shorter the better,
the smaller the number of typing characters, the better,
and the smaller space in view of the same function, the better

It needs better reasoning than "smaller == better" and how it will work with bindings, templates and other features that don't exist in CSS. By adding CSS support it might end with the same situation of bloated syntax, but in other way.

Also many CSS features (or even CSS-preprocessor features) can be ported to the Avalonia. Like inner styles. For some of them we already have feature requests, for other you can create new.

@cyraid
Copy link

cyraid commented May 16, 2023

@maxkatz6
It's easier to find employees that have CSS knowledge, and many teams already have people that are comfortable with CSS vs XML (and learning how XAML does it).

@Armando-CodeCafe
Copy link

Armando-CodeCafe commented Dec 28, 2024

Bumping this after 5 years because even tho it is closed i still really see the value in this. I think if the community comes together to think about this well this would be a very good addition to the framework, for both readability, developer friendlyness and also makes it more accessible to webdevs.

My Idea for Avalonia Cascading Style Sheet files (.acss):

Instead of a direct usage of css where there are alot of nuances to catch, we make an adjusted version of css called acss that only implements the functionality needed for avalonia.

Most of the things would be the same but small things here and there might be more avalonia specific (for example selectors and property setters for keyframes)

The 2 solutions to getting this to work is either making a parser that converts this acss format into an xml style control (which imo is not the way to do this)

or

make adjustments in the framework so a StyleInclude can also be used on these acss files and they will be parsed themselves.

Some possible syntax and their xml counterparts

Lets say i have a simple login page that uses MVVM bindings for the text fields:

  <Grid Classes="login-container">
    <!-- Logo -->
    <Image Classes="logo" Source="logo.png" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,20,0,0"/>

    <!-- Login Form -->
    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Width="300">
      <TextBlock Classes="form-title" Text="Welcome Back" HorizontalAlignment="Center"/>
      
      <TextBox Classes="input-field" PlaceholderText="Username" Margin="0,10,0,0"/>
      <TextBox Classes="input-field" PlaceholderText="Password" Margin="0,10,0,0" PasswordChar="*"/>
      
      <Button Classes="primary-button" Content="Login" Margin="0,20,0,0"/>
      <Button Classes="secondary-button" Content="Forgot Password?" HorizontalAlignment="Center" Margin="0,10,0,0" />
    </StackPanel>
  </Grid>

i would style it like this:

/* Root container styling */
.login-container {
    background-color: #f5f5f5;
    padding: 20; /* Margin-like behavior */
    grid-row-definitions: Auto,*; /* Similar to grid-template-rows */
    grid-column-definitions: Auto; /* Single column */
    horizontal-alignment: Center;
    vertical-alignment: Center;
}

/* Logo styling */
.logo {
    max-width: 100; /* Limiting size without px units */
    max-height: Auto; /* Maintain aspect ratio */
    horizontal-alignment: Center;
    vertical-alignment: Top;
    margin: 0 0 20 0;
}

/* Title styling */
.form-title {
    font-size: 24; /* Points, as Avalonia interprets */
    font-weight: Bold;
    text-color: #333333;
    margin-bottom: 20; /* Space between the title and next element */
    horizontal-alignment: Center;
}

/* Input field styling */
.input-field {
    width: 300; /* Fixed width in points */
    height: Auto; /* Allow height to be intrinsic */
    padding: 10;
    border-brush: #CCCCCC;
    border-thickness: 1;
    corner-radius: 5;
    font-size: 16;
    background-color: #FFFFFF;
    text-color: #333333;
}

/* Primary button */
.primary-button {
    background-color: #0078D7;
    text-color: White;
    border-thickness: 0;
    padding: 10 20; /* Horizontal padding */
    corner-radius: 5;
    font-size: 16;
    horizontal-alignment: Stretch; /* Fully span the container width */
}

.primary-button:hover {
    background-color: #0053A4;
}

/* Secondary button */
.secondary-button {
    background-color: Transparent;
    text-color: #0078D7;
    border-thickness: 0;
    font-size: 14;
    text-decoration: Underline;
    horizontal-alignment: Center;
}

.secondary-button:hover {
    text-color: #0053A4;
}

Why This Makes Sense for Avalonia

  1. No Absolute Units:

    • All dimensions (e.g., width, padding) are specified using values like 20 instead of 20px.
    • This reflects Avalonia's use of relative and absolute point-based dimensions for layout.
  2. Alignment and Margins:

    • Avalonia prefers explicit properties like HorizontalAlignment, VerticalAlignment, and Margin for positioning and spacing.
    • Used these instead of position or flex equivalents.
  3. No Percentages for Layout:

    • The layout is managed via Grid and StackPanel, where row/column definitions (Auto, *) handle dynamic sizing.
    • Buttons and elements stretch to fill their containers using HorizontalAlignment: Stretch.
    • Space for a possible css grid repeat option to get autogenerated rows or columns based on some kind of value and a repeat count
  4. Hover and Interactive States:

    • Pseudo-classes like :hover map well to Avalonia's existing style triggers (e.g., Button:hover), making it intuitive for developers to define interactive states.
  5. Intrinsic Sizing:

    • Elements like TextBox and Button adapt to their content by default (Height: Auto), and width is explicitly set where necessary.

###Keyframing

/* Animation for button hover */
@keyframes button-hover-animation {
    0% {
        background-color: #0078D7;
        transform: scale(1);
    }
    50% {
        background-color: #0053A4;
        transform: scale(1.05);
    }
    100% {
        background-color: #0078D7;
        transform: scale(1);
    }
}

.primary-button:hover {
    animation: button-hover-animation 0.3s ease-in-out;
}

becomes

<Style Selector="Button.primary-button:hover">
    <Style.Animations>
        <Animation Duration="0:0:0.3" Easing="EaseInOut">
            <KeyFrame Cue="0%">
                <Setter Property="Background" Value="#0078D7"/>
                <Setter Property="RenderTransform" Value="ScaleTransform">
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                </Setter>
            </KeyFrame>
            <KeyFrame Cue="50%">
                <Setter Property="Background" Value="#0053A4"/>
                <Setter Property="RenderTransform" Value="ScaleTransform">
                    <ScaleTransform ScaleX="1.05" ScaleY="1.05"/>
                </Setter>
            </KeyFrame>
            <KeyFrame Cue="100%">
                <Setter Property="Background" Value="#0078D7"/>
                <Setter Property="RenderTransform" Value="ScaleTransform">
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                </Setter>
            </KeyFrame>
        </Animation>
    </Style.Animations>
</Style>

What i havent tought about yet

  • How will bindings be effected by this?
  • With the current code base, how hard would it be to add support for style sheets?
  • How can this be implemented in a scalable way so that any newer controls, properties etc will automatically work in the acss format?

My question to you

  • Is there enough interest for something like this
  • Would it be hard to implement and if so, why?
  • Some other things i might not have tought about that could clash with avalonia features (maybe things like themes?)

@wdcossey
Copy link
Contributor

Bumping this after 5 years because even tho it is closed i still really see the value in this. I think if the community comes together to think about this well this would be a very good addition to the framework, for both readability, developer friendlyness and also makes it more accessible to webdevs.

My Idea for Avalonia Cascading Style Sheet files (.acss):

Instead of a direct usage of css where there are alot of nuances to catch, we make an adjusted version of css called acss that only implements the functionality needed for avalonia.

Most of the things would be the same but small things here and there might be more avalonia specific (for example selectors and property setters for keyframes)

The 2 solutions to getting this to work is either making a parser that converts this acss format into an xml style control (which imo is not the way to do this)

or

make adjustments in the framework so a StyleInclude can also be used on these acss files and they will be parsed themselves.

Some possible syntax and their xml counterparts

Lets say i have a simple login page that uses MVVM bindings for the text fields:

  <Grid Classes="login-container">

    <!-- Logo -->

    <Image Classes="logo" Source="logo.png" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,20,0,0"/>



    <!-- Login Form -->

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Width="300">

      <TextBlock Classes="form-title" Text="Welcome Back" HorizontalAlignment="Center"/>

      

      <TextBox Classes="input-field" PlaceholderText="Username" Margin="0,10,0,0"/>

      <TextBox Classes="input-field" PlaceholderText="Password" Margin="0,10,0,0" PasswordChar="*"/>

      

      <Button Classes="primary-button" Content="Login" Margin="0,20,0,0"/>

      <Button Classes="secondary-button" Content="Forgot Password?" HorizontalAlignment="Center" Margin="0,10,0,0" />

    </StackPanel>

  </Grid>

i would style it like this:

/* Root container styling */

.login-container {

    background-color: #f5f5f5;

    padding: 20; /* Margin-like behavior */

    grid-row-definitions: Auto,*; /* Similar to grid-template-rows */

    grid-column-definitions: Auto; /* Single column */

    horizontal-alignment: Center;

    vertical-alignment: Center;

}



/* Logo styling */

.logo {

    max-width: 100; /* Limiting size without px units */

    max-height: Auto; /* Maintain aspect ratio */

    horizontal-alignment: Center;

    vertical-alignment: Top;

    margin: 0 0 20 0;

}



/* Title styling */

.form-title {

    font-size: 24; /* Points, as Avalonia interprets */

    font-weight: Bold;

    text-color: #333333;

    margin-bottom: 20; /* Space between the title and next element */

    horizontal-alignment: Center;

}



/* Input field styling */

.input-field {

    width: 300; /* Fixed width in points */

    height: Auto; /* Allow height to be intrinsic */

    padding: 10;

    border-brush: #CCCCCC;

    border-thickness: 1;

    corner-radius: 5;

    font-size: 16;

    background-color: #FFFFFF;

    text-color: #333333;

}



/* Primary button */

.primary-button {

    background-color: #0078D7;

    text-color: White;

    border-thickness: 0;

    padding: 10 20; /* Horizontal padding */

    corner-radius: 5;

    font-size: 16;

    horizontal-alignment: Stretch; /* Fully span the container width */

}



.primary-button:hover {

    background-color: #0053A4;

}



/* Secondary button */

.secondary-button {

    background-color: Transparent;

    text-color: #0078D7;

    border-thickness: 0;

    font-size: 14;

    text-decoration: Underline;

    horizontal-alignment: Center;

}



.secondary-button:hover {

    text-color: #0053A4;

}

Why This Makes Sense for Avalonia

  1. No Absolute Units:

    • All dimensions (e.g., width, padding) are specified using values like 20 instead of 20px.

    • This reflects Avalonia's use of relative and absolute point-based dimensions for layout.

  2. Alignment and Margins:

    • Avalonia prefers explicit properties like HorizontalAlignment, VerticalAlignment, and Margin for positioning and spacing.

    • Used these instead of position or flex equivalents.

  3. No Percentages for Layout:

    • The layout is managed via Grid and StackPanel, where row/column definitions (Auto, *) handle dynamic sizing.

    • Buttons and elements stretch to fill their containers using HorizontalAlignment: Stretch.

    • Space for a possible css grid repeat option to get autogenerated rows or columns based on some kind of value and a repeat count

  4. Hover and Interactive States:

    • Pseudo-classes like :hover map well to Avalonia's existing style triggers (e.g., Button:hover), making it intuitive for developers to define interactive states.
  5. Intrinsic Sizing:

    • Elements like TextBox and Button adapt to their content by default (Height: Auto), and width is explicitly set where necessary.

###Keyframing

/* Animation for button hover */

@keyframes button-hover-animation {

    0% {

        background-color: #0078D7;

        transform: scale(1);

    }

    50% {

        background-color: #0053A4;

        transform: scale(1.05);

    }

    100% {

        background-color: #0078D7;

        transform: scale(1);

    }

}



.primary-button:hover {

    animation: button-hover-animation 0.3s ease-in-out;

}

becomes

<Style Selector="Button.primary-button:hover">

    <Style.Animations>

        <Animation Duration="0:0:0.3" Easing="EaseInOut">

            <KeyFrame Cue="0%">

                <Setter Property="Background" Value="#0078D7"/>

                <Setter Property="RenderTransform" Value="ScaleTransform">

                    <ScaleTransform ScaleX="1" ScaleY="1"/>

                </Setter>

            </KeyFrame>

            <KeyFrame Cue="50%">

                <Setter Property="Background" Value="#0053A4"/>

                <Setter Property="RenderTransform" Value="ScaleTransform">

                    <ScaleTransform ScaleX="1.05" ScaleY="1.05"/>

                </Setter>

            </KeyFrame>

            <KeyFrame Cue="100%">

                <Setter Property="Background" Value="#0078D7"/>

                <Setter Property="RenderTransform" Value="ScaleTransform">

                    <ScaleTransform ScaleX="1" ScaleY="1"/>

                </Setter>

            </KeyFrame>

        </Animation>

    </Style.Animations>

</Style>


What i havent tought about yet

  • How will bindings be effected by this?

  • With the current code base, how hard would it be to add support for style sheets?

  • How can this be implemented in a scalable way so that any newer controls, properties etc will automatically work in the acss format?

My question to you

  • Is there enough interest for something like this

  • Would it be hard to implement and if so, why?

  • Some other things i might not have tought about that could clash with avalonia features (maybe things like themes?)

Another option:
Write a Roslyn (Incremental) Source Generator that would parse the [Avalonia CSS] .acss file and convert that into an Avalonia class style?

This way you (probably) won't need any changes to the Avalonia source, and it's all done at compile time. Thus it could be a 3rd party tool that can be developed and maintained outside of the main repo.

Happy to help write this if this is the route you'd like to look into?

PS: I haven't looked at the source in a couple years, I could be mistaken with any assumptions that I may have made above.

@Armando-CodeCafe
Copy link

How exactly did u have that in mind? that the roslyn gen creates c# files that contain the style info instead of the xml? I rarely used Styles in code behind so im not sure what the code for that is like in the background. However about roslyn in general if thats a possibility that would also be a good option, but im curious about how maintainable that will be.

If you or someone else has some tips we could go on for that im also willing to go that route.

In principle all the source gen would do is create the style classes? by tokenizing and parsing the acss file?

couple of things that would be quite important to note is that its important to think about capitalization in the syntax,

xml uses pascal case for attributes while css uses kebab or camel more often.

@Armando-CodeCafe
Copy link

Armando-CodeCafe commented Dec 28, 2024

Or wait reading your reply again did u mean class styles as in the xml files containing a style?

cuz then it would be possible to have some kind of placeholder inside of the xml that looks like this:

<CascadingStyle Source="path/to/acss" />

then the source gen would parse that tag, grab the attributes like the path it needs, generate the source from that acss and then convert it then paste it over where the placeholder is,

@wdcossey
Copy link
Contributor

Or wait reading your reply again did u mean class styles as in the xml files containing a style?

cuz then it would be possible to have some kind of placeholder inside of the xml that looks like this:

<CascadingStyle Source="path/to/acss" />

then the source gen would parse that tag, grab the attributes like the path it needs, generate the source from that acss and then convert it then paste it over where the placeholder is,

I am away on holiday at the moment, I can have a look at this as soon as I''m back (next Wednesday). What I'll do [while I'm away] is have a look through the Avalonia source; see if it's even possible to use a Source Generator to do this.

@Armando-CodeCafe
Copy link

Or wait reading your reply again did u mean class styles as in the xml files containing a style?
cuz then it would be possible to have some kind of placeholder inside of the xml that looks like this:
<CascadingStyle Source="path/to/acss" />
then the source gen would parse that tag, grab the attributes like the path it needs, generate the source from that acss and then convert it then paste it over where the placeholder is,

I am away on holiday at the moment, I can have a look at this as soon as I''m back (next Wednesday). What I'll do [while I'm away] is have a look through the Avalonia source; see if it's even possible to use a Source Generator to do this.

Alright, im also gonna check it out, my main worry right now is wether or not an incremental source generator would even be able to read the acss as to my knowledge they are mostly meant for creating not parsing.

In the meantime im gonna write up a proper acss format so that the actual stylesheet implementation makes sense.

@Armando-CodeCafe
Copy link

Reading up on source gens ive found that this might be a possibility:

public class MySourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Initialization logic (e.g., checking if files are present)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Loop through all the additional files (including those in the assets directory)
        foreach (var additionalFile in context.AdditionalFiles)
        {
            var filePath = additionalFile.Path;
            var fileContent = File.ReadAllText(filePath);

            // Process file content as needed
            Console.WriteLine($"Processing file: {filePath}");
            // You can parse or analyze the file content here
        }
    }
}

all that would be needed is that some kind of asset folder in the project exists and contains the styles and would be linked in csproj:

<ItemGroup>
  <AdditionalFiles Include="Assets\**\*" />
</ItemGroup>

this way it could just be as simple as creating a template for an avalonia project that has a folder like that built in (or let the user make it themselves) and any acss files in said folder will be part of the style generator, then its just roslyn reading it from there.

@Armando-CodeCafe
Copy link

Reading up on source gens ive found that this might be a possibility:

public class MySourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Initialization logic (e.g., checking if files are present)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Loop through all the additional files (including those in the assets directory)
        foreach (var additionalFile in context.AdditionalFiles)
        {
            var filePath = additionalFile.Path;
            var fileContent = File.ReadAllText(filePath);

            // Process file content as needed
            Console.WriteLine($"Processing file: {filePath}");
            // You can parse or analyze the file content here
        }
    }
}

all that would be needed is that some kind of asset folder in the project exists and contains the styles and would be linked in csproj:

<ItemGroup>
  <AdditionalFiles Include="Assets\**\*" />
</ItemGroup>

this way it could just be as simple as creating a template for an avalonia project that has a folder like that built in (or let the user make it themselves) and any acss files in said folder will be part of the style generator, then its just roslyn reading it from there.

My only concern would be how to actually access the axaml file from the source gen tho?

@Armando-CodeCafe
Copy link

Reading up on source gens ive found that this might be a possibility:

public class MySourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Initialization logic (e.g., checking if files are present)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Loop through all the additional files (including those in the assets directory)
        foreach (var additionalFile in context.AdditionalFiles)
        {
            var filePath = additionalFile.Path;
            var fileContent = File.ReadAllText(filePath);

            // Process file content as needed
            Console.WriteLine($"Processing file: {filePath}");
            // You can parse or analyze the file content here
        }
    }
}

all that would be needed is that some kind of asset folder in the project exists and contains the styles and would be linked in csproj:

<ItemGroup>
  <AdditionalFiles Include="Assets\**\*" />
</ItemGroup>

this way it could just be as simple as creating a template for an avalonia project that has a folder like that built in (or let the user make it themselves) and any acss files in said folder will be part of the style generator, then its just roslyn reading it from there.

My only concern would be how to actually access the axaml file from the source gen tho?

<ItemGroup>
  <AdditionalFiles Include="**\*.axaml" />
</ItemGroup>

Would work i guess, my concern is how to export the generated styles back into axaml

@Armando-CodeCafe
Copy link

After looking at the source and how avalonia actually compiles the axaml files i dont think it will be possible (or atleast easy) to output the styles back into the xaml, so the only way would be to create the styling from c# after reading from the xaml where to get the files from and then somehow place them into the intialization of the window, but it seems that InitializeComponent() isnt virtual so we would have to actually add the code into the class probably

@wdcossey
Copy link
Contributor

wdcossey commented Dec 29, 2024

This is purely a proof-of-concept; what you see below is hard-coded within the source generator (as I don't have time to write a parser for a (custom) .css file. I simply picked the ButtonsPage from the ControlCatalog to show this PoC.

using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Generators;

namespace ControlCatalog.Pages
{
    [CssGenerator]
    public partial class ButtonsPage : UserControl
    {
        private int repeatButtonClickCount = 0;

        public ButtonsPage()
        {
            InitializeComponent();
            this.Get<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public void OnRepeatButtonClick(object? sender, object args)
        {
            repeatButtonClickCount++;
            var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");
            textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
        }
    }
}

Source Generated code (this is simply hard-coded in the generator [as PoC])
Swapped out Gray for Lime

// <auto-generated/>

using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;

namespace ControlCatalog.Pages;

partial class ButtonsPage
{
    public override void EndInit()
    {
        var headerBorderStyle = new Style(x => x.OfType<Border>().Class("header-border"));
        headerBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Lime, 0.5)));
        headerBorderStyle.Setters.Add(new Setter(Border.BorderBrushProperty, new SolidColorBrush(Colors.Lime)));
        headerBorderStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(0.5)));
        headerBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(5.0, 5.0, 0.0, 0.0)));
        headerBorderStyle.Setters.Add(new Setter(Border.MaxWidthProperty, 450.0));
        headerBorderStyle.Setters.Add(new Setter(Decorator.PaddingProperty, new Thickness(10.0)));
        
        this.Styles.Add(headerBorderStyle);
        
        var headerTextStyle = new Style(x => x.OfType<TextBlock>().Class("header"));
        headerTextStyle.Setters.Add(new Setter(TextBlock.FontSizeProperty, 18.0));
        headerTextStyle.Setters.Add(new Setter(TextBlock.FontWeightProperty, FontWeight.Bold));
        
        this.Styles.Add(headerTextStyle);
        
        var thinBorderStyle = new Style(x => x.OfType<Border>().Class("thin"));
        thinBorderStyle.Setters.Add(new Setter(Border.BorderBrushProperty, new SolidColorBrush(Colors.Lime)));
        thinBorderStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(0.5)));
        thinBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(0.0, 0.0, 5.0, 5.0)));
        thinBorderStyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0.0, 0.0, 0.0, 15.0)));
        
        this.Styles.Add(thinBorderStyle);
    }
}

Commenting out the Style(s) in the .xaml file.

  <!--<UserControl.Styles >

    <Style Selector="Border.header-border">
      <Setter Property="Background">
        <Setter.Value>
          <SolidColorBrush Color="Gray" Opacity="0.5" />
        </Setter.Value>
      </Setter>
      <Setter Property="BorderBrush" Value="Gray" />
      <Setter Property="BorderThickness" Value="0.5" />
      <Setter Property="CornerRadius" Value="5,5,0,0" />
      <Setter Property="MaxWidth" Value="450" />
      <Setter Property="Padding" Value="10" />
    </Style>

    <Style Selector="TextBlock.header">
      <Setter Property="FontSize" Value="18" />
      <Setter Property="FontWeight" Value="Bold" />
    </Style>

    <Style Selector="Border.thin">
      <Setter Property="BorderBrush" Value="Gray" />
      <Setter Property="BorderThickness" Value="0.5" />
      <Setter Property="CornerRadius" Value="0,0,5,5" />
      <Setter Property="Margin" Value="0,0,0,15" />
    </Style>

  </UserControl.Styles>-->

Why EndInit()... Dunno was in a hurry (purposely avoided using InitializeComponent()), need to pack for a flight! 😆

image

@wdcossey
Copy link
Contributor

Used the Roslyn Source Generator Template [from JetBrains (it's a bit messy)] to create the projects. But this is what I did in a few mins.
wdcossey@9320583

@Armando-CodeCafe
Copy link

Used the Roslyn Source Generator Template [from JetBrains (it's a bit messy)] to create the projects. But this is what I did in a few mins. wdcossey@9320583

Woah nice going! this is definetly already a good starting point and PoC, this is probably also the best way to do it in a Roslyn friendly way.

I think the way forward is actually easier than i tought, instead of adding the style into the axaml, we can just add a value to for example the CssGenerator Attribute as in [CssGenerator("SomestylesheetInsideofAdditionalFilesFolder.acss","Some other stylesheet etc.",...)

this way multiple stylesheets can be used, the view side of things only has to think about giving things the right classes where needed and the code behind will handle all of the styles.

assuming this happens once at initialization time the styles also shouldnt get in the way of any runtime changes.

My proposed plan:

  • Make a formal ACSS Spec covering all of the use cases that a regular style tag would (this would take the longest)
  • Create a new issue+PR / New repo seperate from this one for this ACSS library
  • Create the parser and source generators needed for this project

@kekekeks
Copy link
Member

kekekeks commented Dec 29, 2024

After looking at the source and how avalonia actually compiles the axaml files i dont think it will be possible (or atleast easy) to output the styles back into the xaml

XAML compiler runs strictly after roslyn. We could add support for something like:

class MyStyles : Styles
{
      public MyStyles()
      {
            AvaloniaXamlLoader.LoadFromPrecompiledXaml(this, "<Styles>...</Styles>");
      }
}

where LoadFromPrecompiledXaml would only accept a string constant.

MyStyles will be compiler-visible since it's a pre-existing C# class from xaml compiler point of view.

For prototyping you can use runtime xaml loader.

@Armando-CodeCafe
Copy link

After looking at the source and how avalonia actually compiles the axaml files i dont think it will be possible (or atleast easy) to output the styles back into the xaml

XAML compiler runs strictly after roslyn. We could add support for something like:

class MyStyles : Styles
{
      public MyStyles()
      {
            AvaloniaXamlLoader.LoadFromPrecompiledXaml(this, "<Styles>...</Styles>");
      }
}

where LoadFromPrecompiledXaml would only accept a string constant.

MyStyles will be compiler-visible since it's a pre-existing C# class from xaml compiler point of view.

For prototyping you can use runtime xaml loader.

Hmm, would we be able to load this xaml into the xaml of an already existing actual xaml file or would this work as if its a separate new xaml file?

@wdcossey
Copy link
Contributor

wdcossey commented Jan 2, 2025

Done some initial work into using an IIncrementalGenerator w/ AdditionalTextsProvider and the ExCSS CSS parser [with some minor modifications].
Essentially this outputs a .xaml (next to the source .css), it's not a source generator in the purest form (i.e. doesn't ouput a .cs file) but still works as a proof of concept.

ButtonsPage.xaml.css (input)

Border.header-border {
    background-color: rgba(128, 128, 128, 0.5);
    border-color: lime;
    border-width: 0.5em;
    border-radius: 5px;
    max-width: 450px;
    padding: 10px;
}

TextBlock.header {
    font-size: 18px;
    font-weight: bold;
}


Border.thin {
    border-color: lime;
    border-width: 0.5em;
    border-radius: 0 0 5px 5px;
    margin: 0 0 15px 0;
}

ButtonsPage.g.xaml (output)

<Styles xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style Selector="Border.header-border">
    
        <Setter Property="BorderThickness" Value="0.5 0.5 0.5 0.5"/>
        <Setter Property="MaxWidth" Value="450"/>
        <Setter Property="Padding" Value="10 10 10 10"/>


    </Style>

    <Style Selector="TextBlock.header">
    
        <Setter Property="FontSize" Value="18"/>


    </Style>

    <Style Selector="Border.thin">
    
        <Setter Property="BorderThickness" Value="0.5 0.5 0.5 0.5"/>
        <Setter Property="Margin" Value="0 0 0 15"/>


    </Style>
</Styles>

Sample Usage (this could be added by a source generator [here solely for testing])

private void InitializeComponent()
{
    // Before `AvaloniaXamlLoader.Load()`; to avoid overriding styles defined in `ButtonsPage.xaml`
    var include = new StyleInclude((Uri?)null)
    {
        Source = new Uri("avares://ControlCatalog/Pages/ButtonsPage.g.xaml")
    };
    Styles.Add(include);
    AvaloniaXamlLoader.Load(this);
}

image

Notes:

  • I have not mapped all properties, some are not available in the output .xaml (i.e. Color properties as they require some additional work-arounds).
  • The output ButtonsPage.g.xaml (above) is automatically picked up by Avalonia at build time (as an AvaloniaResource) [and bundled into the output assembly].
  • Would probably be better to create a forked version of ExCSS to use here, optimised for output to Avalonia Style properties.
  • The source .css file is melrely added to the .csproj as AdditionalFiles (so it's picked up by the source generator)
  <ItemGroup>
    <None Remove="Pages\ButtonsPage.xaml.css" />
    <AdditionalFiles Include="Pages\ButtonsPage.xaml.css" >
      <DependentUpon>ButtonsPage.xaml</DependentUpon> <!-- So we can group it in the Solution Explorer-->
    </AdditionalFiles>
  </ItemGroup>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants