Zebble has a modern and highly flexible layout engine which is extremely powerful. This is how it works.
Every view object has certain layout related properties such as Width, Height, X, Y, Padding.Left, Padding.Right, .... These properties are not simple numeric types, but rather of a smart type named Length which is explained below.
Responsive Design is an approach to UI development that makes use of flexible layouts. The goal of responsive design is to build pages that detect the visitor's screen size and orientation and change the layout accordingly. Mobile devices have a very diverse range of sizes. So when designing your app's UI, for things like width, size, location, etc, you should think in terms of RULES rather than FIXED SIZES, to enable your UI to automatically adjust to the target device's screen.
The Length class in Zebble is an abstraction of, well, the length of things such as width or height of objects. It represents a logical size concept and can be set based on any of the following:
In all the scenarios above, except the first one, the actual value of a length object will depend on some other views, which themselves could depend on other views, etc. When you set up the value rule correctly using any of the above options, the Length object will update itself to always be the correct value at each point in time which means you don't have to worry about events, and triggers, and keeping things in sync.
Length has a property named CurrentValue (float) which represents its current logical pixel value at each moment in time. For example to query the exact width of a view object, you can use:
CurrentValue is read only and its value can, and often will, change throughout a rendering cycle. So you should generally avoid using it directly to adjust the size of something else, you are probably making a mistake, and should use the binding expressions instead.
To set an exact value for a length object you can use any of the following methods:
width: 150px; /* px and pt will be interpreted the same */
left: 20px; /* for setting X use the CSS standard attribute of left. Actually you can set X too, and it works, but Visual studio will show a warning :(*/
top: 50px; /* same as above for Y */
To give you a bit of a background, in the past, before the invention of ultra high resolution screens, when you said "20 pixels" it usually meant around one centimetre. That's why people got used to the idea of using "pixels" to talk about sizes. Obviously today it's a different story and every display technology has a completely different physical density. The standard term of "point" was introduced in the Web CSS world to combat this, but people still confuse the two together and use the terms interchangeably.
In your UI thinking, you should only care about the logical pixels. For that reason Zebble treats both px and pt in your CSS code the same and assume you mean logical pixels. It will automatically handle the screen density for you in the render engine.
To set an object's size based on its parent you can use the percentage option. When you do this, the size of the object will be bound to the size of the parent and if that changes, the child's will change automatically too.
Note: Padding & Margin
All objects are considered as "border-box" meaning that padding is contained within the width, not added to it. When you have % based width and height, the percentage will be applied to the parent's size minus its padding. For example
â€‹The same applies to X and Y accordingly. For example you can set an object's X to be 20%, which means its X position will be set to 20% of its parent's width minus its horizontal padding.
Another approach for setting a value for Width, Height is to specify an automatic sizing which is any option of the AutoStartegy enum:
The following values are set as default, which will apply unless your CSS files or custom settings override them.
In other words by default Zebble assumes that the height of an object is meant to grow based on its children. But its width is mean to be limited to its parent. Why? The reason for that is that in terms of mobile UX best practices, horizontal scrolling is generally not ok (except for carousel, etc) while vertical scrolling is natural and expected.
In practice most objects' width will be set based on AutoStrategy.Container as this is the default value. In that case the actual value will be based on the following rules:
When an object's width is set as AutoStrategy.Content it will be based on the following rules:
Calculation of the automatic height follows similar rules as that of Width (see above). Just replace width with height and that you can figure it out.
Sometimes you need an object's size or position to be calculated based on other objects. In such cases logically you'll have an expression, or function that determines you intended value. For example let's say you have objects A and B on top of each other and you always want B's width to match that of A. If you just set B's width to the CURRENT value of A's width, if then A's width is changed in the future (by CSS, code, device rotation, etc) then they will go out of sync.
To solve this problem, the Zebble layout system introduces an incredibly powerful mechanism whereby you will define your width values as time-less expressions (or functions) of other values. Zebble will then take care of calculations, cascading and keepings things up to date automatically.
The following example will bind the Width of B to be always the same as A:
The next example will bind the width of B to be always half that of A. In this example the variable "a" referes to the actual width of A which can change in the future, and yet B will remain in sync with it.
The next example will bind the width of B to be always the same as the Width of A minus the width of another object named C. In the code below, variables "a" and "c" will refer to the always-current values of "A.Width" and "C.Width". The order in which the parameters are defined determines the meaning of the variables in the lambda expression.
This can go on and you can add ass many parameters this way as you want. For example:
When using expressions, sometimes you may want to use variables in your expression which are not necessarily of type Length. For example let's say you have a TextInput named "ti" and you want its Width to match its text size. You can write the following:
In the above example, we have no dependency on another Length object and so no parameter is provided to our expression. However, the expression does depend on two elements which can change, i.e. Text and Font of the TextInput control. To ensure that our expression will get reevaluated in the future upon changes in Font or Text, we need to introduce a dependency to those right after defining the binding. So the correct code would be the following:
You can add special constraints about the minimum or maximum acceptable value for a length. If you specify this then that rule will override the actual value defined by the other methods.
// Now the current value is 100.
myView.Width.MaxLimit = 50;
// Now the current value will be 50
// Now the current value will be 20
myView.Width.MinLimit = 30;
// Now the current value will be 30;