-
Notifications
You must be signed in to change notification settings - Fork 25
Style sheets
Note: style sheets were introduced in 1.8.1.9.4-b1
.
At first, LML might actually seem worse than Java when it comes to repetitive tasks. After all, if you want all your Window
widgets built in the same way (with some specific settings), it takes just one factory method in Java to unify all Window
constructions. Fortunately, LML does not force you to copy and paste the same attributes over and over again for each <window>
tag - quite the opposite. There are multiple ways of limiting - or even removing - the setup boilerplate, and before we get to the (arguably) best one, let's explore the others.
The problem: we want all our windows to have just one column and to center their title.
This is exactly what the custom macros were meant to do: allow you to "insert" code snippets modifiable with some parameters. A basic window macro would look like this:
<:macro alias="window" replace="content">
<window oneColumn="true" titleAlign="center">
{content}
</window>
</:macro>
How does it work? Each time you use <:window>
macro (see example below), it will construct a <window>
tag with oneColumn
and titleAlign
attributes predefined. All of its children tags will be passed to the macro and replace {content}
argument. Basically, this:
<:window>
<label>Will become the content</label>
</:window>
...would be evaluated to this:
<window oneColumn="true" titleAlign="center">
<label>Will become the content</label>
</window>
However, you will quickly notice that additional attributes do not get passed to our <window>
actor tag. Macros do allow to define custom arguments - and even pass their default values - so we can refactor the window macro when needed:
<:macro alias="window" replace="content" oneColumn="true" title="">
<window title="{title}" oneColumn="{oneColumn}" titleAlign="center">
{content}
</window>
</:macro>
<!-- Now we can invoke it like this: -->
<:window title="Custom title">...</:window>
<:window title="Not one column" oneColumn="false">...</:window>
Now we can choose a custom window title and optionally make it non-one-column on demand. If no arguments are passed, the window has empty title and just one column.
As you can imagine, this can quickly get tedious. While macros are powerful and allow to do much more than wrapping of a single actor, they should generally be used for more complex code snippets than this. Especially if you need to use a lot of additional custom attributes.
Arguments in LML can be used pretty much anywhere, including tags. Arguments can be defined at runtime with multiple macros or added before parsing. They use {verySimpleSyntax}
, and you can think of them as simple find-and-replacements. We can define the default attributes as a code snippet inside an argument. You can do it before parsing:
Lml.parser()
// TODO Setup LML parser.
.argument("windowDefaults", "oneColumn=true titleAlign=center").build();
...or at runtime:
<:assign key="windowDefaults">oneColumn="true" titleAlign="center"</:assign>
Now you can use the argument inside all <window>
tags that you want to modify. This breaks XML syntax, but gets the job done.
<window title="Title" {windowDefaults}>
...
</window>
Contrary to macros, arguments do not prohibit you from easily adding new attributes to the <window>
tag without having to modify arguments in the first place. However, modifying argument values is the hard part: you cannot just use some of the attributes defined by an argument, or add any parameters to the argument like you could to a macro. Find-and-replace is completely fine for array iterations, where you just want the current element of an array, but this approach is definitely less useful as a substitute for default attributes.
Fortunately, there's an alternative.
Using LML style sheets, you can choose default values of attributes applied to tags. Basically, if a tag does not explicitly define an attribute, it's default value will be used. (You can think of it as a huge Map<String, Map<String, String>>
, where tags are keys in the first map, and attributes are keys in the second.)
Once you add default values for oneColumn
and titleAlign
attributes to window
tag, you will no longer have to use any specific syntax to apply their values: all <window>
tags will have the values set automatically.
<!-- Automatically "adds" oneColumn=true titleAlign=center: -->
<window>...</window>
<!-- Additional custom attributes: -->
<window title="Title">...</window>
<!-- Overrides oneColumn: -->
<window oneColumn="false">...</window>
That goes without saying, but this is one of the most convenient ways of applying attributes en masse. As usual, there are multiple ways of setting the default attribute values, and you're free to choose which one suits you best.
LmlStyleSheet
instance is responsible for managing so-called styles (default values of attributes). This interface is heavily commented, so you're free to explore the Javadocs, but it basically comes down to this:
String tag = "window";
String attribute = "oneColumn";
String defaultValue = "true";
LmlStyleSheet sheet = lmlParser.getStyleSheet();
sheet.addStyle(tag, attribute, defaultValue);
As you can see, it consumes 3 strings: tag name to modify, attribute name to set and default value to apply. If you find it more convenient, you can also set the styles during parser building:
String tag = "window";
String attribute = "titleAlign";
String defaultValue = "center";
Lml.parser()
// TODO Setup LML parser.
.style(tag, attribute, defaultValue).build();
Since you usually don't want to mix views (LML data) with services (Java code), you should usually avoid managing styles programmatically.
<:style>
macro allows to define styles in LML templates. The macro consumes at least 2 arguments: tag and attribute names. You can pass the default value as third attribute or between macro tags:
<:style window oneColumn true/>
<:style window titleAlign>center</:style>
You can optionally use named attributes to support XML syntax:
<:style tag="window" attribute="oneColumn" value="true"/>
<:style tag="window" attribute="titleAlign">center</:style>
Both tags and attributes accept LML arrays, so you can modify multiple tags and attributes at once:
<:style tag="table;window" attribute="tablePadTop;tablePadBottom" value="4"/>
Again, this is not the fastest or the most pleasant way to define styles, but it allows to modify them at runtime, which might prove very useful. Sometimes you might need to temporarily change default value of some attribute, and this is probably the best way to do that.
Using LML Style Sheet files is the default way of defining styles. LSS supports CSS-like syntax, which allows you to quickly set the default values of attributes. There are no powerful selectors like the ones CSS supports, but it is usually good enough to get the job done.
To set up the <window>
tag like in the other examples, our style sheet would look somewhat like this:
window {
oneColumn: true;
titleAlign: center;
}
To read and apply the file, you need to pass it to the LmlParser
:
lmlParser.parseStyleSheet(Gdx.files.internal("path/to/sheet.lss"));
// Or - parse sheet during building:
Lml.parser()
// TODO Setup LML parser.
.stylesPath("path/to/styles.lss").build();
You can modify multiple tags at once by passing multiple tag names before the styles block. Tag names can be optionally separated with commas.
window, dialog {
oneColumn: true;
titleAlign: center;
}
Inheritance is also supported by adding .
before the inherited tag name. In the example below, all <window>
tag default attributes will be copied to <dialog>
tag. You can inherit from as much tags as you want to.
window {
oneColumn: true;
titleAlign: center;
}
dialog .window {
style: dialog;
}
While this does not exactly match the default CSS behavior, most IDEs with CSS file editors will correctly highlight the syntax.
Comments start with /*
and end with */
. Just so you know, comments should work anywhere. And when I say anywhere, I mean even inside tags, attributes, and default values. Basically, you could put a comment before and after each and every valid character and it would still just work. Comments and not supported with a simple find-and-replace either, so even if you use comments, line numbers in exceptions are still correct. This is a valid LML style sheet:
/* Comment. */ window/* Comment. */,/* Comment. */ dia/* Comment. */log {/* Comment. */
oneColumn: tr/* Comment. */ue;/* Comment. */
title/* Comment. */Align: cen/* Multiline
comment. */ter;
}/* Comment. */
/* Comment. */
Use macros for complex code snippets with multiple actors. Use arguments for simple, repetitive code snippets that don't require any modifications or customization at runtime. Use style sheets to apply default values of attributes across the application.
MJ 2016