Keep UI component in order

User interface has to be adjustable and adaptive to an environment and a hardware where it can be shown. In that respect existence of mobile phones, various WEB browsers, different operation systems, PCs, laptops, tablets and combinations of all these factors makes life harder. To avoid potential UI layouting problems developers should not locate and size UI components by assigning dedicated (x,y) coordinates and (width,height) values. Otherwise even slight font changing can crash designed UI layout.

Layout managers is well known solution to get adaptive UI. That is exactly the thing that helps developing adjustable UI layout. Layout manager doesn’t trust fixed UI components positions and sizes. It uses rules-based manner to order UI components. Layout manager is defined on the level of an UI component and “knows” two important things:

  • How to order UI children components
  • How to compute the component preferred size basing on its children components hierarchy

Technically Zebra layout manager class has to implement the following:

  • “doLayout(target)” method to define rules children components of the given target component has to be laid outed
  • “calcPreferredSize(target)” method to calculate size the given target component desires to have
  • “zebra.layout.Layout” interface should be implemented by a layout manager class

A layout manager can be applied to any Zebra UI component by calling “setLayout(layout)” method. Additionally children UI components can specify extra parameter called constraints that is specific for a particular layout manager. It is can be done either by setting a children component “constraints” field or during insertion of the component to the parent component:

  
    ...
    // create panel 
    var p = new zebra.ui.Panel();

    // set layout manager for the panel
    p.setLayout(new zebra.layout.BorderLayout());

    // add children components 
    p.add(zebra.layout.CENTER, 
          new zebra.ui.Label("Center")); // constraints are passed during insertion 
                                         // the children component 
  
    // another way to specify constraints is filling "constraints" 
    // field of a children component  
    var l = new zebra.ui.Label("Top");
    l.constraints = zebra.layout.TOP;
    p.add(l);
    ...

Implementing simple layout manager

Nevertheless Zebra provides rich set of different predefined layout managers, it makes sense for better understanding to start from developing an own Zebra layout manager. It should help to discover how simple the idea is. As an example, let’s develop layout manager that orders components along parent component diagonal:

// declare layout manager class
var DiagLayout = zebra.Class(zebra.layout.Layout,[
    // define what preferred size the given "target" component wants to have.
    // in this case it calculated as sum of preferred heights and widths of 
    // children components 
    function calcPreferredSize(target) {
       var psW = 0, psH = 0;
       for(var i=0; i < target.kids.length; i++) {
           var kid = target.kids[i];
           if (kid.isVisible) {
               var ps = kid.getPreferredSize();
               psW += ps.width;
               psH += ps.height;
           }
       }
       return { width:psW, height:psH };
    },

    // define rules how children components of the 
    // given "target" have to be ordered 
    function doLayout(target) {
       var x = target.getTop(), y = target.getLeft();
       for(var i=0; i < target.kids.length; i++) {
           var kid = target.kids[i];
           if (kid.isVisible) {
               var ps = kid.getPreferredSize();
               kid.setBounds(x, y, ps.width, ps.height);
               x += ps.width;
               y += ps.height;
           }
       }
    }
]);

Use just developed diagonal layout manager as follow:
    var r = new zebra.ui.zCanvas(200,200).root;
    // set developed above diagonal layout manager
    r.setLayout(new DiagLayout());

    // add children components
    r.add(new zebra.ui.Button("One"));
    r.add(new zebra.ui.Button("Two"));
    r.add(new zebra.ui.Button("Three"));

Zebra predefined layout managers

Below you can find snapshots for all supplied by Zebra layout managers. Every snapshots are provided together with “live” Zebra application that run the snapshot source code directly on this page.

zebra.layout.StackLayout

The layout manager places children UI components over each other and stretches its to fill the whole parent component surface.

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);
        // set stack layout manager
        r.setLayout(new zebra.layout.StackLayout());

        // add button
        r.add(new zebra.ui.Button("Under transparent"));
        
        // add partially transparent panel component
        var p = new zebra.ui.Panel();
        p.setBackground("rgba(240,240,240,0.7)");
        r.add(p);

zebra.layout.BorderLayout

Border layout manager splits component area into five parts: top, left, right, bottom and center. Children components are placed to one of the part basing on constraints that have been specified for them:

        var r = new zebra.ui.zCanvas(200,200).root;
        // set border layout manager
        r.setLayout(new zebra.layout.BorderLayout());

        // add children UI components with different constraints 
        r.add(zebra.layout.CENTER,new zebra.ui.Button("CENTER"));
        r.add(zebra.layout.LEFT, new zebra.ui.Button("LEFT"));
        r.add(zebra.layout.RIGHT,new zebra.ui.Button("RIGHT"));
        r.add(zebra.layout.TOP,new zebra.ui.Button("TOP"));
        r.add(zebra.layout.BOTTOM,new zebra.ui.Button("BOTTOM"));

zebra.layout.PercentLayout

Percent layout manager orders children components basing on percentage sizes of the components. The percentage sizes are defined as the children components constraints.

Horizontally ordered percent layout manager with stretched vertically components

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set percent layout manager that stretches 
        // components vertically and sizes component 
        // horizontally according to its percentage 
        // constraints
        r.setLayout(new zebra.layout.PercentLayout());

        // add button that takes 20% of horizontal space 
        r.add(20, new zebra.ui.Button("20%"));

        // add button that takes 30% of horizontal space        
        r.add(30, new zebra.ui.Button("30%"));
        
        // add button that takes 50% of horizontal space
        r.add(50, new zebra.ui.Button("50%"));

Horizontally ordered percent layout manager with preferred components heights

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set percent layout manager that sizes components
        // vertically according to its preferred heights and  
        // sizes components horizontally according to its
        // percentage constraints 
        r.setLayout(new zebra.layout.PercentLayout(
                    zebra.layout.HORIZONTAL, 2, false));

        // add button that takes 20% of horizontal space 
        r.add(20, new zebra.ui.Button("20%"));

        // add button that takes 30% of horizontal space 
        r.add(30, new zebra.ui.Button("30%"));

        // add button that takes 50% of horizontal space 
        r.add(50, new zebra.ui.Button("50%"));

Vertically ordered percent layout manager with preferred components widths

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set percent layout manager that sizes components
        // horizontally according to its preferred widths and  
        // sizes components vertically according to its
        // percentage constraints 
        r.setLayout(new zebra.layout.PercentLayout(
                    zebra.layout.VERTICAL, 2, false));

        // add button that takes 20% of vertical space 
        r.add(20, new zebra.ui.Button("20%"));
        
        // add button that takes 30% of vertical space 
        r.add(30, new zebra.ui.Button("30%"));

        // add button that takes 50% of vertical space 
        r.add(50, new zebra.ui.Button("50%"));

Vertically ordered percent layout manager with stretched horizontally components

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set percent layout manager that stretches components
        // horizontally and sizes components vertically according
        // to its percentage constraints 
        r.setLayout(new zebra.layout.PercentLayout(
                    zebra.layout.VERTICAL, 2, true));

        // add button that takes 20% of vertical space 
        r.add(20, new zebra.ui.Button("20%"));

        // add button that takes 30% of vertical space 
        r.add(30, new zebra.ui.Button("30%"));

        // add button that takes 50% of vertical space 
        r.add(50, new zebra.ui.Button("50%"));

zebra.layout.FlowLayout

Flow layout provides many possibilities to align children components.

Vertically ordered UI components are centered horizontally and vertically:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with vertical components
        // ordering and center vertical and 
        // horizontal alignments
        r.setLayout(new zebra.layout.FlowLayout(
             zebra.layout.CENTER, 
             zebra.layout.CENTER, 
             zebra.layout.VERTICAL, 2));

        // add children components
        r.add(new zebra.ui.Button("VCentered"));
        r.add(new zebra.ui.Button("VCentered"));
        r.add(new zebra.ui.Button("VCentered"));

Vertically ordered UI components are aligned top-left:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with vertical components 
        // ordering, top-left alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.LEFT,
                           zebra.layout.TOP, 
                           zebra.layout.VERTICAL, 2));

        // add children components
        r.add(new zebra.ui.Button("Left-Top-Ver"));
        r.add(new zebra.ui.Button("Left-Top-Ver"));
        r.add(new zebra.ui.Button("Left-Top-Ver")); 

Vertically ordered UI components are aligned top-right:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with vertical components 
        // ordering, top-right alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.RIGHT, 
                           zebra.layout.TOP, 
                           zebra.layout.VERTICAL, 2));

        // add children components
        r.add(new zebra.ui.Button("Right-Top-Ver"));
        r.add(new zebra.ui.Button("Right-Top-Ver"));
        r.add(new zebra.ui.Button("Right-Top-Ver"));

Vertically ordered UI components are aligned bottom-right:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with vertical components 
        // ordering, bottom-right alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.RIGHT,
                           zebra.layout.BOTTOM,
                           zebra.layout.VERTICAL, 2));

        // add children components
        r.add(new zebra.ui.Button("Right-Bottom-Ver"));
        r.add(new zebra.ui.Button("Right-Bottom-Ver"));
        r.add(new zebra.ui.Button("Right-Bottom-Ver"));

Horizontally ordered UI components are centered vertically and horizontally:

        var r = new zebra.ui.zCanvas(600,120).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with horizontal components 
        // ordering, center-center alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.CENTER, 
                           zebra.layout.CENTER,
                           zebra.layout.HORIZONTAL, 2));

        // add children components
        r.add(new zebra.ui.Button("HCentered"));
        r.add(new zebra.ui.Button("HCentered"));
        r.add(new zebra.ui.Button("HCentered"));

Horizontally ordered UI components are aligned center-left:

        var r = new zebra.ui.zCanvas(600,120).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with horizontal components 
        // ordering, center-left alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.LEFT, 
                           zebra.layout.CENTER, 
                           zebra.layout.HORIZONTAL,2));

        // add children components
        r.add(new zebra.ui.Button("Left-Center-Hor"));
        r.add(new zebra.ui.Button("Left-Center-Hor"));
        r.add(new zebra.ui.Button("Left-Center-Hor"));

Horizontally ordered UI components are aligned center-right:

        var r = new zebra.ui.zCanvas(600,120).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with horizontal components 
        // ordering, center-right alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.RIGHT, 
                           zebra.layout.CENTER, 
                           zebra.layout.HORIZONTAL, 2));

        // add children components
        r.add(new zebra.ui.Button("Right-Center-Hor"));
        r.add(new zebra.ui.Button("Right-Center-Hor"));
        r.add(new zebra.ui.Button("Right-Center-Hor"));

Horizontally ordered UI components are aligned top-right:

        var r = new zebra.ui.zCanvas(600,120).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with horizontal components 
        // ordering, top-right alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.RIGHT, 
                           zebra.layout.TOP, 
                           zebra.layout.HORIZONTAL, 2));

        // add children components
        r.add(new zebra.ui.Button("Right-Top-Hor"));
        r.add(new zebra.ui.Button("Right-Top-Hor"));
        r.add(new zebra.ui.Button("Right-Top-Hor"));

Horizontally ordered UI components are aligned top-left:

        var r = new zebra.ui.zCanvas(600,120).root;
        r.setBorder(zebra.ui.borders.plain);

        // set flow layout with horizontal components 
        // ordering, top-left alignment and 2 pixels
        // gap between inserted components
        r.setLayout(new zebra.layout.FlowLayout(
                           zebra.layout.LEFT, 
                           zebra.layout.TOP, 
                           zebra.layout.HORIZONTAL,2));

        // add children components
        r.add(new zebra.ui.Button("Left-Top-Hor"));
        r.add(new zebra.ui.Button("Left-Top-Hor"));
        r.add(new zebra.ui.Button("Left-Top-Hor"));

zebra.layout.RasterLayout

Raster layout manager is default Zebra component layout manager. It emulates the standard approach where locations and sizes are precisely specified by calling “setLocation(x,y)”, “setSize(w,h)” or “setBounds(x,y,w,h)” methods. It is strongly recommended to avoid using raster layout manager the way developers define exact values for a component location and size. Rules are better in respect of implementing adaptive UI that doesn’t depend on screen resolutions, font metrics, allocated for an UI application size and so on.

Hardcoded, user defined components locations and sizes:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set raster layout is not necessary since 
        // this is default Zebra layout manager 
        // r.setLayout(new zebra.layout.RasterLayout());

        // create component, setup its metrics precisely 
        // and add its to parent component
        var b = new zebra.ui.Button("(10,10,140,40)");
        b.setBounds(10,10,140,40);
        r.add(b);

        var b = new zebra.ui.Button("(10,120,80,50)");
        b.setBounds(10,100,120,50);
        r.add(b);

Raster layout manager also can be less dependent from an environment an UI application can be run:

Size components to its referred size:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set raster layout that sizes children components
        // according to its preferred size
        r.setLayout(new zebra.layout.RasterLayout(
                    zebra.layout.USE_PS_SIZE));

        // add children components
        var b=new zebra.ui.Button("(10,10) Preferred Sized");
        b.setLocation(10,10);
        r.add(b);

        var b = new zebra.ui.Button(
        new zebra.ui.MLabel("(30,100)\nPreferred sized"));
        b.setLocation(30,100);
        r.add(b);

Size components to its preferred size and align its:

        var r = new zebra.ui.zCanvas(200,200).root,
            MLabel=zebra.ui.MLabel; //shortcut to class
        r.setBorder(zebra.ui.borders.plain);

        // set raster layout that sizes children components
        // according to its preferred size
        r.setLayout(new zebra.layout.RasterLayout(
                    zebra.layout.USE_PS_SIZE));

        var b = new zebra.ui.Button("(10,10) Preferred Sized");
        b.setLocation(10,10);
        r.add(b);

        // add top-left aligned button
        var b = new zebra.ui.Button(
            new MLabel("(Left,Bottom)\nPreferred sized"));
        r.add(zebra.layout.LEFT | zebra.layout.BOTTOM, b);

        // add center aligned button 
        var b = new zebra.ui.Button(
            new MLabel("(Center)\nPreferred sized"));
        r.add(zebra.layout.CENTER, b);

        // add right aligned button 
        var b = new zebra.ui.Button(
            new MLabel("(Center)\nPreferred sized"));
        r.add(zebra.layout.RIGHT, b);

zebra.layout.ListLayout

List layout manager orders children component vertically as a list of items.

Children components are stretched horizontally to occupy whole parent container width:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set list layout manager that orders
        // components as list item and stretches 
        // children horizontally 
        r.setLayout(new zebra.layout.ListLayout());

        // add children components
        r.add(new zebra.ui.Button("Item1"));
        r.add(new zebra.ui.Button("Item2"));
        r.add(new zebra.ui.Button("Item3"));

Children components are centered horizontally:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set list layout manager with centered 
        // horizontally children components and
        // 2 pixels gap between the components
        r.setLayout(new zebra.layout.ListLayout(
                    zebra.layout.CENTER,2));

        // add children components
        r.add(new zebra.ui.Button("Item1"));
        r.add(new zebra.ui.Button("Item2"));
        r.add(new zebra.ui.Button("Item3"));

Children components are aligned left:

        var r = new zebra.ui.zCanvas(200,200).root;
        r.setBorder(zebra.ui.borders.plain);

        // set list layout manager with aligned 
        // left children components and
        // 2 pixels gap between the components
        r.setLayout(new zebra.layout.ListLayout(
                    zebra.layout.LEFT, 2));

        // add children components
        r.add(new zebra.ui.Button("Item1"));
        r.add(new zebra.ui.Button("Item2"));
        r.add(new zebra.ui.Button("Item3"));

zebra.layout.GridLayout

Grid layout manager splits a component area to number of virtual cells. Children components are placed into the cells. One cell can be occupied only by one children component. Using “zebra.layout.Constraints” class developers can control how a children component has to be placed inside the virtual cell. “zebra.layout.Constraints” declares the following fields that declares how a component has to be placed inside a virtual cell:

Field Allowed values Description
ax zebra.layout.LEFT
zebra.layout.RIGHT
zebra.layout.CENTER
zebra.layout.STRETCH
Horizontal alignment in cell
ay zebra.layout.TOP
zebra.layout.BOTTOM
zebra.layout.CENTER
zebra.layout.STRETCH
Vertical alignment in cell
top,left,
bottom,right
integer value >= 0 Cell top, left, bottom and right paddings

The picture below explains how a component can be aligned inside a virtual cell controlled by grid layout manager:
gridlayout

Default grid layout manager constraints

        var r = new zebra.ui.zCanvas(150,150).root;
        r.setBorder(zebra.ui.borders.plain);
        r.setLayout(new zebra.layout.GridLayout(2,2));

        // add children components
        r.add(new zebra.ui.Button("1x1"));
        r.add(new zebra.ui.Button("1x2"));
        r.add(new zebra.ui.Button("2x1"));
        r.add(new zebra.ui.Button("2x2"));

1. Custom grid layout manager constraints

        var r = new zebra.ui.zCanvas(400,150).root,
            ctr = new zebra.layout.Constraints();
        ctr.setPadding(8);

        r.setBorder(zebra.ui.borders.plain);
        r.setLayout(new zebra.layout.GridLayout(2,2));
        r.add(ctr,new zebra.ui.Button("1x1 Long component"));
        r.add(ctr,new zebra.ui.Button("1x2"));
        r.add(ctr,new zebra.ui.Button("2x1"));
        r.add(ctr,new zebra.ui.Button("2x2"));

2. Custom grid layout manager constraints

        var r = new zebra.ui.zCanvas(400,200).root,
            ctr = new zebra.layout.Constraints(),
            MLabel = zebra.ui.MLabel; // class shortcut
        ctr.setPadding(8);

        r.setBorder(zebra.ui.borders.plain);
        r.setLayout(new zebra.layout.GridLayout(2,2));

        var ctr2=new zebra.layout.Constraints(zebra.layout.CENTER,zebra.layout.BOTTOM);

        ctr2.setPadding(8);
        r.add(ctr2,new zebra.ui.Button("1x1 bottom component"));

        r.add(ctr,new zebra.ui.Button(new MLabel("1x2\nnew line\nnew line")));

        r.add(new zebra.layout.Constraints(zebra.layout.CENTER,zebra.layout.CENTER), 
              new zebra.ui.Button("Centered"));

        r.add(ctr, new zebra.ui.Button(new MLabel("2x2\n2x2\n2x2")));

Layout package API and constants

All zebra layout managers are hosted in “zebra.layout” package. The package provides the core “zebra.layout.Layoutable” class. This class describes a rectangular object that is bound with the given size and location. “zebra.layout.Layoutable” component can contain other layoutable components as its children. The children components are laid outed with a layout manager. Pay attention “zebra.layout” package is completely independent from UI part. Developer can easily use it as basis for layout management, for instance, for WEB based elements. Zebra UI engine just extends the basic “zebra.layout.Layoutable” class with visual and event related stuff.

The package provides number of useful API methods that can be handy to manipulate with component hierarchy:

API method Description
zebra.layout.getDirectChild(p,k) get immediate kid for the given parent and children component
zebra.layout.getDirectAt(x,y,p) get immediate kid located at the given location of the specified parent component
zebra.layout.getTopParent(comp) get top parent by the given component. Top is a component whose parent is "null"
zebra.layout.toParentOrigin(x,y,c, [p]) translate the given relative location into a parent relative location
zebra.layout.toChildOrigin(x,y,c,p) convert the given component location into relative location of the specified children component successor
zebra.layout.isAncestorOf(p,k) test if the given kid component is an ancestor of the specified parent component

Also it declares number of constants that from time to time have to be used as a layout constraints:

Constraints Description
zebra.layout.LEFT left constraint or alignment
zebra.layout.RIGHT right constraint or alignment
zebra.layout.TOP top constraint or alignment
zebra.layout.BOTTOM bottom constraint or alignment
zebra.layout.CENTER center constraint or alignment
zebra.layout.HORIZONTAL horizontal constraint or alignment
zebra.layout.VERTICAL vertical constraint or alignment
zebra.layout.STRETCH stretch constraint
zebra.layout.USE_PS_SIZE use preferred size
zebra.layout.TLEFT top left constraint or alignment
zebra.layout.TRIGHT top right constraint or alignment
zebra.layout.BLEFT bottom left constraint or alignment
zebra.layout.BRIGHT bottom right constraint or alignment
constraints