Good looking using Pliant UI
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.
Pliant UI styling machinery
- 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.
'style use' is very fast and consistent.
'style mset' is fast but hard to use and inconsistent.
'style set' is slow but consistent.
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.
We have 'padding/left' 'padding/top' 'padding/right' and 'padding/bottom' that add some space (specified in millimeters) around the content.
Then, we have a very important 'mode' parameter that defines what we draw:
draw 'r1' rectangle.
draw 'r1' and 'r2' rectangles. If 'r2' color is undefined, then 'r2' will define a hole in the 'r1' rectangle.
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.
How to translate UI style to HTML CSS is explained in the document about the HTTP proxy.
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
img setup (image_prototype -10 -10 10 10 256 256 color_gamut:"rgb") ""
for (var Int y) 0 255
for (var Int x) 0 255
var ColorRGB888 c := color rgb x y 0
img write x y 1 addressof:c
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.
As far as size and alignment are concerned, the size of the image on the UI client depends only on the dimensions, here 20 by 20 millimeters (20 = 10 - (-10) ), not the number of pixels the image contains (here 256 by 256).
The alignment will be the text baseline will be aligned with the image vertical 0 position, so here with the middle of the image.
'image_define' can be provided extra options.
As an example:
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).
'pack4' could be used instead of 'jpeg' to request to use Pliant PACK4 encoding (an advanced two dimensions run length compression).
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.
Instead of having to define an image object, then load it, then transfer it, then insert it, a single shortcut command is provided:
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
fill (color rgb 255 0 0)
angle -10 -10
point 2 -2 tg_in 0 -1 auto_out
point -2 2 in 5 0 out -10 0
through 0 -1
through 1 0
through 0 1
through -1 0
'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.
'angle' means that the point has no imposed in and out tangents. 'through' means that the point will have auto computed in and out tangents in order to provide smooth path through the specified position.
Lastly, 'point' is a more general version that enables to provide explicit in and out tangents. There are three ways to define each tangent:
'in' or 'out' means that both direction and length of the tangent is provided (a longer tangent means that the curve will remain close to the tangent for a longer time)
'tg_in' or 'tg_out' means that the direction of the tangent is provided through a vector, but it's length will be computed automatically.
'auto_in' or 'auto_out' means that both tangent direction and length are to be computed automatically.
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.
Pliant vector drawing library is very mature, but the glue code to enable using it in an UI based application is both incomplete and unsatisfying. Will have to be reworked.
When done, I'll document with greater details.