UI hierarchy rendering

Zebkit UI components hierarchy is rendered on (HTML5) Canvas surface. Painting procedure starts traversing UI hierarchy (where every UI component is instance of “zebkit.ui.Panel” class) from zCanvas then switches to its child components and so on recursively. For every UI component in rendered hierarchy the painting procedure does the following:

As it has been shown above rendering is localized with zebkit UI component. If a new fancy UI component has to be implemented and the component has to draw an own unique view, then do the following:

Re-painting

As it has been shown before, zebkit UI component should implement dedicated API paint method or methods to draw desired view. The open question is:

First of all UI components never call implemented paint methods (“paint(g)”, “update(g)”, “paintOnTop(g)”) directly. It is up to zebkit painting procedure to call the methods when it has been requested, at proper time with properly prepared 2D context.

Secondly painting procedure is initiated on-demand. UI components are provided with “repaint()” or “repaint(z, y, width, height)” panel API method that is supposed to request a component re-painting. If a component changes its visual state, the component has to inform zebkit that the component or a part of the component have to be re-painted. To do it the component has to call “repaint(…)” method. For example:

zebkit.require("ui", "layout", function(ui, layout) {
    var MyUIComponent = zebkit.Class(ui.Panel, [
        function paint(g) {
            // draw something with the given color
            g.setColor(this.color);
            g.drawLine(...);  
            ...
        },
        // define "color" visual property 
        function setColor(c) {
            this.color = c;
            this.repaint(); // force the component repainting
                            // since its visual state has been 
                            // updated  
        }
    ]);
    ...
})

Paint methods implementation

As it has been mentioned before, to customize a component rendering the component can implement one of the following method or its combination:

Let’s implement step by step all these three methods to draw “gray” cross on “red” background marked with “white” border:

paint(g)

Inherit the basic top level “zebra.ui.Panel” UI component class and implement “paint(g)” method that draws gray cross:

Paint method implementation
Update method implementation
Paint on top method implementation
zebkit.require("ui", "layout", function(ui, layout) {
    // inherit "zebkit.ui.Panel" and implement "paint()"
    ui.PaintComponent=zebkit.Class(ui.Panel,[
        function paint(g) {
           g.setColor("lightGray");
           g.fillRect(8,this.height/3,
                      this.width-16,this.height/4);
           g.fillRect(this.width/3,8,
                      this.width/4,this.height-16);
        }
    ]);
    // create canvas and add just developed component
    var c = new ui.zCanvas(150,150);
    c.root.setLayout(new layout.BorderLayout());
    c.root.add("center", new ui.PaintComponent());
});

update(g)

Inherit developed on previous step “PaintComponent” class and extend it with “update(g)” method that fills the component background with red color:

zebkit.require("ui", "layout", function(ui, layout) {
    // extend "PaintComponent" class with "update" method
    ui.UpdateComponent=zebra.Class(ui.PaintComponent,[
        function update(g) {
           g.setColor("red");
           g.fillRect(0,0,this.width, this.height);
        }
    ]);
    // create canvas and add just developed component
    var c = new ui.zCanvas(150,150);
    c.root.setLayout(new layout.BorderLayout());
    c.root.add("center", new ui.UpdateComponent());
});

paintOnTop(g)

Inherit developed on previous step “UpdateComponent” class and extend it with “paintOnTop(g)” method that draws white rectangle over gray cross:

zebkit.require("ui", "layout", function(ui, layout) {
    // extend "UpdateComponent" class with "paintOnTop"
    // method
    ui.PaintOnTopComponent=zebkit.Class(ui.UpdateComponent,[
        function paintOnTop(g) {
           g.setColor("white");
           g.lineWidth = 3;
           g.rect(3,3,this.width-6, this.height-6);
           g.stroke();
        }
    ]);
    // create canvas and add just developed component
    var c = new ui.zCanvas(150,150);
    c.root.setLayout(new layout.BorderLayout());
    c.root.add("center", new ui.PaintOnTopComponent());
});

UI component decorative elements

Zebkit UI component has number of predefined decorative elements that can be customized:

UI component shape

By default zebkit component is a rectangle with the given width and height. Component shape customization can be done with setting a border view that has to implement “outline(g,x,y,w,h,d)” method. The outline method setups closed path in passed 2D context that defines the shape and returns true if the context path has to be applied as a shape.

For instance let’s create round zebkit UI component. As the first step let’s implement a view that setups round 2D path:

zebkit.require("ui", "draw", "layout", function(ui, draw, layout) {
    var RoundShape = zebkit.Class(draw.View, [
        function outline(g,x,y,w,h,d) {
            if (w === h) {
                g.beginPath();
                g.arc(Math.floor(x + w/2) + (w % 2 === 0 ? 0 : 0.5),
                      Math.floor(y + h/2) + (h % 2 === 0 ? 0 : 0.5),
                      Math.floor((w - 1)/2), 0, 2 * Math.PI, false);
                g.closePath();
            } else {
                g.ovalPath(x,y,w,h);
            }
            return true; // say to apply it as shape
        }
    ]); 
});

Then let’s use the developed above shape to a component:

zebkit.require("ui", "draw", "layout", function(ui, draw, layout) {
    ...
    var r = new ui.zCanvas("roundShape", 300, 300).root;
    r.setLayout(new layout.BorderLayout());
    r.add(new ui.Panel({
        border: new RoundShape(),  // set shape via border
        background: new draw.Gradient("red", "orange")
    }));
});

The result is shown below:

Custom shape

If the shaped component needs to handle input events, it is necessary to aware events manager which points belong to the component taking in account its round shape. It has to be done with “contains(x, y)” method implementation. The method returns true if the given point is inside the component shape. Let’s extending our example with possibility to change background color depending where mouse pointer is located:

zebkit.require("ui", "draw", "layout", function(ui, draw, layout) {
    ...
    r.add(new ui.Panel({
        border: new RoundShape(),
        background: new draw.Gradient("red", "orange")
    }, 
    [
        // say if the given point is inside component
        function contains(x, y) {
            var a = this.width / 2, b = this.height / 2;
            x -= a; y = -y + b;
            return (x * x)/(a * a) + (y * y)/(b * b) <= 1;
        },
        // handle pointer entered event
        function pointerEntered(e) {
            this.setBackground(new draw.Gradient("orange", "blue"));  
        },
        // handle pointer exited event
        function pointerExited(e) {
            this.setBackground(new draw.Gradient("red", "orange"));
        }
    ]));
});

The result is shown below:

Custom shape