Difference between revisions of "Custom CSS Property"
(Created page with "TODO!") |
|||
(8 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | Custom CSS properties can be a great way to reuse functionality and extend PowerUI. They're designed to be simple to make, so simple copy and pasting skills or basic coding skills can be all that's required. Let's get started! | |
+ | |||
+ | == A basic property == | ||
+ | |||
+ | The most basic CSS property (A C# file) looks like this: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | using Css; | ||
+ | |||
+ | // hello-world | ||
+ | public class HelloWorld: CssProperty{ | ||
+ | |||
+ | public override string[] GetProperties(){ | ||
+ | |||
+ | // One or more names (sometimes prefixed) that your property handles: | ||
+ | return new string[]{"hello-world"}; | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | At the very least, you must: | ||
+ | |||
+ | * Inherit from an appropriate type of CSS property (it'll almost always be CssProperty that you use) | ||
+ | * Give it one or more CSS property names. | ||
+ | * Give it a class name, which is typically just the main name of the property. | ||
+ | * Optionally put it in the Css.Properties namespace. | ||
+ | |||
+ | Place it somewhere outside the PowerUI folders. This is just incase you update PowerUI, as you won't want to loose your changes. It can go anywhere you find appropriate. | ||
+ | |||
+ | == Initial value and inheritance == | ||
+ | |||
+ | Typically you'll want to define an initial value (default '''none''') and if your property inherits or not (default '''no'''). That's done in the constructor: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | // Constructor for the above CSS property | ||
+ | public HelloWorld(){ | ||
+ | |||
+ | // Either set it as a Css value: | ||
+ | // (such as the common 'auto' one, made available as AUTO for convenience) | ||
+ | // InitialValue=AUTO; | ||
+ | |||
+ | // Or as text: | ||
+ | InitialValueText="0 50%"; | ||
+ | |||
+ | // Does it inherit? | ||
+ | Inherits=true; | ||
+ | |||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | == A normal value == | ||
+ | |||
+ | If you'd like your property to accept the 'normal' keyword then you'd override '''GetNormalValue'''. It's 0 by default. | ||
+ | |||
+ | == Aliases == | ||
+ | |||
+ | An aliased property is once which updates the internal value of some other set value. For example 'scroll-top' is an alias for scroll[0]. In CSS there are 4 main types: | ||
+ | |||
+ | * Point aliases. property-x and property-y. | ||
+ | * Colour aliases. property-r, property-g, property-b and property-a. | ||
+ | * Square aliases. property-top, property-left, property-bottom, property-right. | ||
+ | * Logical aliases. property-block-start, property-inline-end, property-block-end, property-inline-start. | ||
+ | |||
+ | Note that they alias in the order seen above. I.e. property-b is property[3]. To setup these common aliases: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | // Called to setup aliases. | ||
+ | public override void Aliases(){ | ||
+ | |||
+ | // Only call the one(s) you actually want | ||
+ | // (you _could_ call them all, but it would probably be confusing!) | ||
+ | |||
+ | // Point (x,y): | ||
+ | // PointAliases2D(); | ||
+ | |||
+ | // Point (x,y,z): | ||
+ | // PointAliases3D(); | ||
+ | |||
+ | // Colour: | ||
+ | // ColourAliases(); | ||
+ | |||
+ | // Square (top,left,bottom,right) *and* logical: | ||
+ | // SquareAliases(); | ||
+ | |||
+ | // Something else - see below for more! | ||
+ | Alias("some-random-css-property",ValueAxis.None,0); | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | Above we're also mapping 'some-random-css-property' to the 0th index of 'this' property (the one that -x, -top, -r etc also map to). You also specify which axis it uses too - that's important for resolving %. | ||
+ | |||
+ | For example, text-indent is an alias of padding[3] (the same as padding-left). If you used a % in text-indent, you would expect it to be ''a % of the width'' (the x or horizontal axis). To make that real, take a look at padding.cs where you'll see this: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | // text-indent maps to padding-left, aka index 3 of padding: | ||
+ | Alias("text-indent",ValueAxis.X,3); | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Composite Properties == | ||
+ | |||
+ | Composite properties set multiple CSS properties all at once. background, border, animation etc are all examples of composite properties. In this case, you derive from CssCompositeProperty instead, and either define the formal syntax as a ''specification'' or manually parse your value. | ||
+ | |||
+ | Here's how we define a formal syntax: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | using Css; | ||
+ | |||
+ | // hello-composition basics (using a spec) | ||
+ | public class HelloComposition : CssCompositeProperty{ | ||
+ | |||
+ | // GetProperties is the same: | ||
+ | public override string[] GetProperties(){ | ||
+ | return new string[]{"hello-composition"}; | ||
+ | } | ||
+ | |||
+ | // Define the specification: | ||
+ | protected override Spec.Value GetSpecification(){ | ||
+ | |||
+ | // Let's define the composite formal syntax as this: | ||
+ | /* | ||
+ | [hello-composition-height || hello-composition-style] | ||
+ | */ | ||
+ | |||
+ | // To represent the above, we declare it like so: | ||
+ | return new Spec.AnyOf( | ||
+ | new Spec.Property(this, "hello-composition-height"), | ||
+ | new Spec.Property(this, "hello-composition-style") | ||
+ | ); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | For this specification form to work, you must also '''declare GetSpecification on all of the properties''' that are referenced. In the example above, that means you'd need to define both ''hello-composition-height'' and ''hello-composition-style'' as normal properties, but also have GetSpecification on both of them. The specification of those properties simply depends on which values they accept. | ||
+ | |||
+ | Simple composite example: text-decoration (and the two properties it references) | ||
+ | Complex composite example: font | ||
+ | |||
+ | |||
+ | == Formal Syntax and specifications == | ||
+ | |||
+ | ''(If you're wondering what these are, take a look at, for example, [https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration#Formal_syntax the MDN article for text-decoration])''. | ||
+ | |||
+ | When converting a standard CSS formal syntax into a spec object, use this handy cheatsheet: | ||
+ | |||
+ | {| class="wikitable" | ||
+ | |- | ||
+ | ! Operator | ||
+ | ! Class to use | ||
+ | |- | ||
+ | | a || b | ||
+ | | Spec.AnyOf | ||
+ | |- | ||
+ | | a b | ||
+ | | Spec.All | ||
+ | |- | ||
+ | | <a> | ||
+ | | Spec.Property | ||
+ | |- | ||
+ | | a? | ||
+ | | Spec.Optional | ||
+ | |- | ||
+ | | a | b | ||
+ | | Spec.OneOf | ||
+ | |- | ||
+ | | / | ||
+ | | Spec.Literal | ||
+ | |- | ||
+ | | a && b | ||
+ | | Spec.AllAnyOrder | ||
+ | |- | ||
+ | | {1,4} | ||
+ | | Spec.Repeated | ||
+ | |- | ||
+ | | a* | ||
+ | | Spec.Repeated (use 0,Infinity) | ||
+ | |- | ||
+ | | a+ | ||
+ | | Spec.Repeated (use 1,Infinity) | ||
+ | |- | ||
+ | | <number>, <percent> etc | ||
+ | | Spec.ValueType(typeof(Css.Units.PercentUnit)) | ||
+ | |} | ||
+ | |||
+ | Declaring a specification is generally convenient as that's how they're actually defined. However, if you want to parse the value in a custom way (there are a few properties which can't be defined using the web specification format), use '''OnReadValue''' instead. ''animation'' is an example of a CSS property that does that. | ||
+ | |||
+ | == Knowing when the computed value of your property changed == | ||
+ | |||
+ | Whenever a CSS property is recomputed, it fires the '''Apply''' method: | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | |||
+ | public override ApplyState Apply(ComputedStyle style,Value value){ | ||
+ | |||
+ | // This property has changed on the given computed style! | ||
+ | // Properties that render things add or remove a Renderer to the renderable data. | ||
+ | // Take a look at the background-color property for example. | ||
+ | |||
+ | // Unless you're caching the value as something else, | ||
+ | // you'll almost always just return Ok: | ||
+ | return ApplyState.Ok; | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Finding built-in properties == | ||
+ | |||
+ | Currently there are 3 major groups of CSS properties in PowerUI: | ||
+ | |||
+ | * Core - Path/To/PowerUI/Spark/Properties | ||
+ | * SVG extensions - Path/To/PowerUI/Source/SVG/Css Properties | ||
+ | * MathML extensions - Path/To/PowerUI/Source/Extras/MathML/Css Properties | ||
+ | |||
+ | == Checking your property is available == | ||
+ | |||
+ | An easy sanity check is to make sure your new property is being correctly detected by PowerUI. To do that: | ||
+ | |||
+ | * Go to Window > PowerUI > Supported CSS | ||
+ | * Look for e.g. your-property-name | ||
+ | |||
+ | If PowerUI is successfully detecting your property, it will be visible on this list. |
Latest revision as of 21:58, 12 March 2017
Custom CSS properties can be a great way to reuse functionality and extend PowerUI. They're designed to be simple to make, so simple copy and pasting skills or basic coding skills can be all that's required. Let's get started!
Contents
A basic property
The most basic CSS property (A C# file) looks like this:
using Css;
// hello-world
public class HelloWorld: CssProperty{
public override string[] GetProperties(){
// One or more names (sometimes prefixed) that your property handles:
return new string[]{"hello-world"};
}
}
At the very least, you must:
- Inherit from an appropriate type of CSS property (it'll almost always be CssProperty that you use)
- Give it one or more CSS property names.
- Give it a class name, which is typically just the main name of the property.
- Optionally put it in the Css.Properties namespace.
Place it somewhere outside the PowerUI folders. This is just incase you update PowerUI, as you won't want to loose your changes. It can go anywhere you find appropriate.
Initial value and inheritance
Typically you'll want to define an initial value (default none) and if your property inherits or not (default no). That's done in the constructor:
// Constructor for the above CSS property
public HelloWorld(){
// Either set it as a Css value:
// (such as the common 'auto' one, made available as AUTO for convenience)
// InitialValue=AUTO;
// Or as text:
InitialValueText="0 50%";
// Does it inherit?
Inherits=true;
}
A normal value
If you'd like your property to accept the 'normal' keyword then you'd override GetNormalValue. It's 0 by default.
Aliases
An aliased property is once which updates the internal value of some other set value. For example 'scroll-top' is an alias for scroll[0]. In CSS there are 4 main types:
- Point aliases. property-x and property-y.
- Colour aliases. property-r, property-g, property-b and property-a.
- Square aliases. property-top, property-left, property-bottom, property-right.
- Logical aliases. property-block-start, property-inline-end, property-block-end, property-inline-start.
Note that they alias in the order seen above. I.e. property-b is property[3]. To setup these common aliases:
// Called to setup aliases.
public override void Aliases(){
// Only call the one(s) you actually want
// (you _could_ call them all, but it would probably be confusing!)
// Point (x,y):
// PointAliases2D();
// Point (x,y,z):
// PointAliases3D();
// Colour:
// ColourAliases();
// Square (top,left,bottom,right) *and* logical:
// SquareAliases();
// Something else - see below for more!
Alias("some-random-css-property",ValueAxis.None,0);
}
Above we're also mapping 'some-random-css-property' to the 0th index of 'this' property (the one that -x, -top, -r etc also map to). You also specify which axis it uses too - that's important for resolving %.
For example, text-indent is an alias of padding[3] (the same as padding-left). If you used a % in text-indent, you would expect it to be a % of the width (the x or horizontal axis). To make that real, take a look at padding.cs where you'll see this:
// text-indent maps to padding-left, aka index 3 of padding:
Alias("text-indent",ValueAxis.X,3);
Composite Properties
Composite properties set multiple CSS properties all at once. background, border, animation etc are all examples of composite properties. In this case, you derive from CssCompositeProperty instead, and either define the formal syntax as a specification or manually parse your value.
Here's how we define a formal syntax:
using Css;
// hello-composition basics (using a spec)
public class HelloComposition : CssCompositeProperty{
// GetProperties is the same:
public override string[] GetProperties(){
return new string[]{"hello-composition"};
}
// Define the specification:
protected override Spec.Value GetSpecification(){
// Let's define the composite formal syntax as this:
/*
[hello-composition-height || hello-composition-style]
*/
// To represent the above, we declare it like so:
return new Spec.AnyOf(
new Spec.Property(this, "hello-composition-height"),
new Spec.Property(this, "hello-composition-style")
);
}
}
For this specification form to work, you must also declare GetSpecification on all of the properties that are referenced. In the example above, that means you'd need to define both hello-composition-height and hello-composition-style as normal properties, but also have GetSpecification on both of them. The specification of those properties simply depends on which values they accept.
Simple composite example: text-decoration (and the two properties it references) Complex composite example: font
Formal Syntax and specifications
(If you're wondering what these are, take a look at, for example, the MDN article for text-decoration).
When converting a standard CSS formal syntax into a spec object, use this handy cheatsheet:
Operator | Class to use |
---|---|
a || b | Spec.AnyOf |
a b | Spec.All |
<a> | Spec.Property |
a? | Spec.Optional |
a | b | Spec.OneOf |
/ | Spec.Literal |
a && b | Spec.AllAnyOrder |
{1,4} | Spec.Repeated |
a* | Spec.Repeated (use 0,Infinity) |
a+ | Spec.Repeated (use 1,Infinity) |
<number>, <percent> etc | Spec.ValueType(typeof(Css.Units.PercentUnit)) |
Declaring a specification is generally convenient as that's how they're actually defined. However, if you want to parse the value in a custom way (there are a few properties which can't be defined using the web specification format), use OnReadValue instead. animation is an example of a CSS property that does that.
Knowing when the computed value of your property changed
Whenever a CSS property is recomputed, it fires the Apply method:
public override ApplyState Apply(ComputedStyle style,Value value){
// This property has changed on the given computed style!
// Properties that render things add or remove a Renderer to the renderable data.
// Take a look at the background-color property for example.
// Unless you're caching the value as something else,
// you'll almost always just return Ok:
return ApplyState.Ok;
}
Finding built-in properties
Currently there are 3 major groups of CSS properties in PowerUI:
- Core - Path/To/PowerUI/Spark/Properties
- SVG extensions - Path/To/PowerUI/Source/SVG/Css Properties
- MathML extensions - Path/To/PowerUI/Source/Extras/MathML/Css Properties
Checking your property is available
An easy sanity check is to make sure your new property is being correctly detected by PowerUI. To do that:
- Go to Window > PowerUI > Supported CSS
- Look for e.g. your-property-name
If PowerUI is successfully detecting your property, it will be visible on this list.