-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Comments
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. |
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. |
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 from your another comment:
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. |
@maxkatz6 |
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 counterpartsLets 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
###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
My question to you
|
Another option: 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. |
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. |
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:
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. |
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 |
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 |
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) 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]) // <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 |
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. |
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:
|
XAML compiler runs strictly after roslyn. We could add support for something like: class MyStyles : Styles
{
public MyStyles()
{
AvaloniaXamlLoader.LoadFromPrecompiledXaml(this, "<Styles>...</Styles>");
}
} where
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? |
Done some initial work into using an 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);
} Notes:
<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> |
how can i use css files instead of xaml files?
The text was updated successfully, but these errors were encountered: