The picture below shows side view of UI component. The rectangular parts express different painted component areas:

paintw

The power of Zebra UI components is ability to control its rendering. Any imaginable and desirable UI component can be drawn. The painting process is split to sequence of steps that are demonstrated at the picture above:

  • Step I Draw component background view if it has been defined for the given UI component. Background view can be clipped by border view. It happens if: border view has been specified and border view implements “outline(…)” method
  • Step II Draw component border view if it has been defined for the given UI component
  • Step III If UI component implements “update(…)” method call it to fill background with a custom content. The clipping area is set to full UI component size
  • Step IV If UI component implements “paint(…)” method call it to render the component “face” content. The clipping area is cut by the the component border gaps and paddings
  • Step V Recursively paints all children components following the sequence of steps listed above
  • Step VI If component implements “paintOnTop(…)” method, call it to render necessary decorative elements over component surface and its rendered children components. The clipping area is set to full UI component size
  • Step VII That is all ! :)

Component rendering customization is quite simple thing. Let’s implement step by step al 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:

// inherit "zebra.ui.Panel" and implement "paint()"
var PaintComponent=zebra.Class(zebra.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 zebra.ui.zCanvas(150,150);  
c.root.setLayout(new zebra.layout.BorderLayout());
c.root.add(zebra.layout.CENTER, 
           new 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:

// extend "PaintComponent" class with "update" method
var UpdateComponent=zebra.Class(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 zebra.ui.zCanvas(150,150);  
c.root.setLayout(new zebra.layout.BorderLayout());
c.root.add(zebra.layout.CENTER,
           new UpdateComponent());

paintOnTop(g)

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

// extend "UpdateComponent" class with "paintOnTop"
// method
var PaintOnTopComponent=zebra.Class(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 zebra.ui.zCanvas(150,150);  
c.root.setLayout(new zebra.layout.BorderLayout());
c.root.add(zebra.layout.CENTER, 
           new PaintOnTopComponent());

In real case there is no necessity to develop three classes. All paint methods can be implemented in one class or even in an anonymous class “on the fly” as it is demonstrated below:


var c = new zebra.ui.zCanvas(150,150);  
c.root.setLayout(new zebra.layout.BorderLayout());
c.root.add(zebra.layout.CENTER, 
   new Panel([    // instantiate anonymous UI panel class 
      // implement paint method                    
      function paint(g) {  ...},

      // implement update method
      function update(g) { ... },

      // implement paint on top method
      function paintOnTop(g) { ... }
]));

Valid paint state

What is going on with rendering when a developer change component size, location, background, visibility and so on? Who is responsible to force an UI component to be repainted? This is importnat question for every UI framework. The brief answer in Zebra framework context is: Zebra takes responsibility to repaint UI components whenever it is necessary. In most cases developers should not care and think about it.

Mor detailed pictures and deeper explanation is illustrated at the picture below:

paintman

Zebra UI component rendering is done by special Zebra manager – paint manager. UI components itself are responsible only for providing:

  • necessary painting methods: “paint”, “update”, “updateOnTop”
  • reference to some decorative elements views like border and background
  • metrics like gaps, size, location, preferred size, etc

UI Component is not aware how it should be painted. It is up to paint manager to render a hierarchy of UI components that sits on the given canvas. The following actions trigger paint manager to start drawing an UI components hierarchy:

  • An UI components even has occurred. Paint manager listens component events by registering itself as a component listener in Zebra event manager. For instance if an UI component has been resized paint manager immediately gets the component sized event, than calculates dirty area and initiates the UI hierarchy repainting. Dirty area is a rectangular area on the given canvas that has to be redrawn
  • An UI component requests repainting UI component can trigger repainting by calling “repaint()” or “repaint(x,y,w,h)” methods. These methods inform paint manager that the whole or part of the given UI component is “dirty” and have to be redrawn
        var panel = new zebra.ui.Panel();   
        ...
        var c = new zebra.ui.zCanvas();
        c.setLayout(new zebra.layout.BorderLayout());
        c.add(zebra.layout.CENTER, panel);
    
        ...
        panel.repaint();
    

UI component “repaint(…)” methods are useful if you are developing an own UI components and needs to sync the component properties updating with valid paint state. Imagine you are implementing simple UI component that renders marker whenever it gets focus. The marker color is a property of the component that can be set by “setMarkerColor(color)” method. As you can see there are two places where “repaint()” method has to be called:

  • “setMarkerColor(color)” method. Every time a user set a new marker color the component has to be refreshed to be repainted with the new marker color
  • Focus lost and gained events. The marker border has to be visible only if the component holds focus. Every time the component gained or lost focus the component view has to be updated.
    var FocusMarkerComponent = zebra.Class(zebra.ui.Panel,[
         // say the component can have focus
         function canHaveFocus() { return true; },
     
         // override "focused()" method that is called every time
         // the component gained or lost focus  
         function focused() {
             this.$super(); // don't forget call super "focused()" method
             this.repaint();// trigger the component repaint
         },

         // "markerColor" property value setter
         function setMarkerColor(c) {
             this.markerColor = c;
             this.repaint();
         },

         // implement "paint(...)" method to draw focus marker rectangle
         // whenever the component captures the focus
         function paint(g) {
             if (this.hasFocus()) {
                 // set "red" as default color if "markerColor" has not 
                 // been defined yet
                 g.setColor(this.markerColor==null?"red":this.markerColor);
                 g.rect(3,3, this.width-6, this.height-6);
                 g.stroke();
             }
         }  
    ]);   

    var c = new zebra.ui.zCanvas();
    c.root.setLayout(new zebra.layout.BorderLayout());
    c.root.add(zebra.layout.CENTER, new FocusMarkerComponent());