Is Pliant UI styling a good model ?
Why didn't you designed a good styling model ?
The ultimate goal of styling was to separate the code producing the content (semantic) from the code driving the rendering engine. Let's show how hard it can be through three examples.
First, let's imagine that we define a new 'important' semantic attribute, and that our document contains several kind of informations, let's say 'definition' 'demonstration' 'note'. If we decide to use different text colors for the different kind of informations, then we might expect the 'important' attribute to not only turn the text to bold, but also to make the text color more saturated. In other words, the color of the text marked with 'important' attribute depends on the tag ('definition' 'demonstration' or 'note') the 'important' attributes takes place in. So semantic content is not a flat set of markers, but rather nested, and the final rendering of some text depends on all the pile of tags it appears in, not only the innermost one.
Then, imagine a query that returns some informations about your company running orders. At semantic level, the result of the query it is a set of records (one record per order). So, it could be displayed as a table. Now, in order to get some good looking result, if the number of informations in each record is too large to be properly displayed on a single line, several fields will be displayed in a single cell, on several lines. It means reordering and grouping when translating from fields to cells. A mechanism such as providing the semantic as an HTML table with a class attribute for each row and each cell, then applying an HTML CSS is not be enough to do that.
Let's push to the limit with the third example: when we display a chart, the content should be sent as a table, and the styling machinery should turn the table to a nice looking chart, just like in a desktop spreadsheet.
I conclude that if the goal is to truly separate the semantic from the rendering, then the ultimate styling mechanism has to be handled on server side because it will have to do application specific computations and I don't want a language on client side.
So, the next question is: how powerful a styling mechanism should be provided on client side ?
I've chosen to implement a Pliant UI style as the set of all rendering parameters for all tags. It satisfies both previously expressed constrains since, on server side a style needs to be defined only once, then using it just requires to provide the style identifier as opposed to all the rendering parameters it sets, and on client side, switching to another style just means changing the current style pointer.
The drawback is that Pliant styling system sucks from the consistency point of view as soon as the application defines new semantic tags. Each new semantic tag should add some rendering parameters to the UI style data structure, so that when switching from a style to another, the rendering parameters be properly defined both outside and inside the new semantic tag. In other words, a new semantic tag is a new widget so would need it's own set of rendering parameters in each Pliant style.
As a summary, Pliant UI styling design policy is a bit: If you can't make it powerful, make it fast.
- Some styling features are still buggy in Pliant release 101. Please upgrade to match the following documentation -
The widgets implemented in Pliant UI currently are:
A set of rendering parameters is associated with each of them. The set of all rendering parameters for all widgets is called a style. Each style is identified by a string identifier. The default style is identified by empty string identifier.
Defining a new style is two steps. First create if through copying an existing one:
style_copy "" "foo"
Then change some parameters through using several time 'style_set' function:
style_set "foo" "para/text/italic" true
Using the new style is achieved through:
style use "foo"
Well, that was the entry point. Let's now explain that it's not that simple.
A UI style defines all rendering parameters for all widgets. It is implemented as a data structure defined in /pliant/graphic/layout/style.pli, named LayoutStyle that provides a large set of parameters the various widgets (paragraph, title, button, etc) will use at positioning and drawing time.
As shown in the provided listing, that's a lot of parameters in facts, so that changing one thing, let's say the buttons text size 'thing', requires to change several values (text size for all possible contexts). Another example would be changing the table cell padding 'thing' which require changing the left, right, top and bottom padding parameters.
So the grouping name notion has been introduced. A grouping name is referring to several styling parameters. You can see the predefined ones on the listing lines starting with '--'. It is also possible to define new names and associate them with several parameters through using 'style_define' instruction several times:
style_define "myid" "para/text/italic"
This example means that 'myid' name now refers to all parameters it already referred to (none if it's a new keyword as in this example) plus all the ones 'para/text/italic' refers to (a single one since 'para/text/italic' is the name of a real styling parameters, not a grouping name).
Then, the '*' sign can be used to automatically generate grouping names (only one '*' sign can be used). As an example, the following code would change the ground color of buttons in all contexts at once:
style_set "" "button/*/box/r2/color" (color rgb 192 64 64)
Now, that we have fully explained how to cleanly define styles, let's explain how to dirtily modify styling in the middle of an application using several parameters in a 'style' instruction. Yes, it should not be done, but that's real life.
style set "table/padding" 3 set table "table/ground/color" (color rgb 255 192 192)
Basically, the current style is modified on the fly while executing the underlying bloc.
Please don't mistake 'style_set' function which is used to modify a style declaration with 'style set' control which is used to temporary modify the current style while rendering the bloc.
Pliant UI 'style' instruction applies to all the subtree, so it is not convenient if what we want to style is just the box around some content:
style use "mybox"
So, 'mark' and 'recall' options have been added (starting from release 103):
style mark "anid" use "mybox"
'mark' will save the current style pointer under the specified name, and 'recall' will pick it back.
We have seen the clean grouping name notion. Let's introduce the dirty shared parameters notion.
We will start through refining the semantic of 'style set' instruction we introduced at the beginning of the styling machinery presentation. If we write:
style_set "foo" "para/text/italic" true
we not only change some parameters in the 'foo' style, but also change parameters sharing.
Let explain the effect of parameters sharing with a few examples:
style_define "highlight" "para/text/bold"
Since 'highlight' has been defined as a grouping name, the 'Hello' word will be displayed bold and italic.
Now, if keep the same beginning, but change the end with:
style set "para/text/bold" true
Then the 'Hello' word will be bold only.
Now the trick is using 'mset' instead of 'set':
style mset "para/text/bold" true
And the result is a bold and italic word.
As opposed to 'set', 'mset' does not change the parameters links, but rather overwrites the parameters values. Since the instruction 'style_set "" "highlight" false' linked both 'para/text/bold' and 'para/text/italic' parameters to the same value, overwriting the value of one of them with 'mset' changes both parameters at once. 'mset' stands for multiple set.
Another important thing to specify about parameters sharing is that 'style_copy' creates a new style with all parameters shared with the source one. Using 'style_set' will later link some of the parameters to different values.
Shared parameters notion seems hard to use and inconsistent. What is it useful for ?
As a summary:
Coffee time, then I will explain the semantic of various styling parameters.
Let me remind you that the list of all available styling parameters is not included in this document, because it's too long, but available as a separate document.
First of all, many widgets, such as 'button' or 'para' or 'table/cell' are using a set of parameters to define a surrounding box. Drawing the box is implemented in /pliant/graphic/layout/helper/draw.pli.
The rectangle coordinates 'left' 'top' 'right' and 'bottom' are specified in the millimeters, and go the opposite side of padding, so start (zero value) from the padded area of the box and grow (positive value) to the center of the box. Then 'round' enables to get rounded corners (also expressed in millimeters), and 'angles' enables to discard angles rounding in some of the corners. 1 for the top left corner, 2 for the top right one, 4 for the bottom left, and 8 for the bottom right corner. Of course, several corners rounding can be discarded through adding these values.
For the 'button' and 'link' widget, 'spacing' means the space between the label and the keyboard key combination associated with the button (when the key is not part of the label, so is displayed at the right of the label).
For 'para' 'title' and 'header' widgets, 'spacing' means line spacing. 1 means standard line as specified in the font definition.
For 'table' widget, 'box' parameters define the padding and the table external border (or ground), whereas 'cell' and 'header' ones define them for individual cells (depending if they have the 'header' flag set or not).
For all widgets, 'css' specifies the value to add to the HTML tag as the 'class' attribute.
The following module has to be included in order to access images related UI instructions:
Let's assume that the application created an image, maybe through:
var Link:ImagePrototype img :> new ImagePixmap
Then, transferring the image to the UI client is done through:
image_define "img1" img
and finally, inserting the image in the middle of the current paragraph, just if it was a single character, is done through:
We use two instructions, one for transferring and one for using, so that the same image can be reused at several places in the page content without transferring it several time. The identifier, here 'img1' can be freely selected and is used only to identify the image on the UI.
'image_define' can be provided extra options.
image_define "img1" img "jpeg quality 0.9"
instructs the server to send the image to the UI client as an JPEG compressed image (consumes less bandwidth if the image is a photo) with JPEG quality set to 0.9 (quality is in the 0 to 1 range).
As opposed to most other UI instructions, 'image_define' server side instruction does not translate to sending a single PML encoded instruction to the UI client. Instead it sends 'image_define' first in order to define the prototype of the new image (dimensions, number of pixels, pixel encoding), then it sends either 'image_write_raw' or 'image_write_pack4' or 'image_write_jpeg' to send pixels.
Very often, people just want to pick an image from a file, and display it.
image_inline_file "bug" "/pliant/welcome/image/bug.jpeg"
The size of the image depends on the image resolution in the file. The alignment will be on zero point, which for most on disk file formats means top left corner. If different alignment is expected, an extra parameter shall be provided to image_inline_file:
image_inline_file "bug" "/pliant/welcome/image/bug.jpeg" "center 0.5 0.5"
'center' will force the zero point in the image, 0 0 means top left, 0 1 means bottom left, 0.5 0.5 means center, etc.
Lastly, 'image_copy' can be used to copy an area of an image to another image, or a different area in the same image:
image_copy "img1" 200 200 300 300 "img2" 120 130
means, copy a 100 by 100 area of 'img1' starting at (top left corner) point 200 200 to the 100 by 100 area in 'img2' starting at (top left corner) point 120 130.
The following module has to be included in order to access vector drawing UI instructions:
Here is just a tiny vector drawing sample:
draw -10 -10 10 10
'draw' is used to introduce some vector drawing. A vector drawing is considered by the positioning engine as a single characters, just like an image. Provided parameters are borders coordinates in millimeters.
'fill' draws an outline. An outline is made of one or several curves. Pliant curves can use either Pliant native formula, or Bezier, or Conics. See literature and 'pos' method in /pliant/math/curve.pli for extra details. Also, at the moment, only Pliant native curve formula and even-odd filling rule are mapped from Pliant vector drawing layer to Pliant UI.
'curve' is used to define a single curve. Each point of the curve is defined through either 'angle' or 'through' or 'point' instruction.
It should now be clear that:
angle 10 20
is a shortcut of
point 10 20 tg_in 0 0 tg_out 0 0
through 10 20
is a shortcut of
point 10 20 auto_in auto_out
Only filling is supported at the moment. No text and no image is supported, so it's mostly unusable.