Table of Contents:

  1. Prerequisites
  2. Building cZUI
  3. Your first zoomable interface
  4. Types of scene nodes
  5. Subscribing to events
  6. Cameras and windows
  7. Views and drivers
  8. An advanced example

Prerequisites

To build and use cZUI you will need the following:

Building cZUI

Linux: ./configure, make, make install
Windows: see msvc directory

Your first zoomable interface

The following is the code for two cameras showing the same text at varying zoom level. Text gets highlighted at mouseover. Note that the text gets highlighted in both cameras.

#include <cZUI/cZUI.h>				// general client header
#include <cZUI/drivers/sdsgesdl.h>		// video driver
#include <cZUI/drivers/edsdl.h>			// event driver
#include <cZUI/contrib/activeleaf.h>		// easy-to-use leaf, we inherit from it
#include <cZUI/graphwindow.cpp>			// Handles all cameras that paint in one window


#include <cZUI/scenenode.h>		// Leaf nodes cannot be placed directly
					// into the scene. We need a scenenode
					// above every leaf.

#include "alloc_list.h"			// keeps track of allocated nodes 

class TextLeaf : public ActiveLeaf
{
protected:
	 char *txt;
public:
	TextLeaf( double px, double py, 
		const char *t ) 
		: ActiveLeaf(px,py)
	{
		txt = new char[ strlen(t) + 1 ];
		strcpy( txt, t );
	};
	void paint( Camera *c )
	{
		ScrollableView *v = c->get_view();
		v->pen( 0,0,0, 1 );	// color:black, thickness:1
		v->setfontsize( 8 );
		v->textout( xpos, ypos+8, txt );
	};
	void paint_hover( Camera *c )
	{
		ScrollableView *v = c->get_view();
		v->setfontsize( 8 );
		v->pen( 120,0,0, 1 );	// color:darkred, thickness:1
		v->textout( xpos, ypos+8, txt );
	};
	void get_boundrect( double &bx1, double &by1, 
		double &bx2, double &by2 )
	{
		bx1 = xpos;
		by1 = ypos;
		bx2 = bx1 + strlen(txt)*5.5;
		by2 = ypos + 8;
	};
	bool check_event_fitspixels( Event * )
	{
		// always catch mouse events
		return true;
	};
};

int main( int argc, char* argv[] )
{
	alloc_list heap;
	if( SDL_Init( SDL_INIT_VIDEO /*| SDL_INIT_NOPARACHUTE*/ ) )
	{
		std::cout << "Problems initializing video, exiting" << std::endl;
		abort();
	}

	unsigned long framecnt = 0;

	// Open a window of 640x480, 16bit.
	SDL_Surface *s1 = SDL_SetVideoMode(400, 240, 16, 0 /*SDL_RESIZABLE*/);
	// And create a driver that uses it.
	SDLSGESurfaceDriver driver1(s1);
	// The second camera uses the same window
	SDLSGESurfaceDriver driver2(s1);
	
	// create the scene
	Scene sc;

	// A Frame-rate window, 100 FPS
	GraphWindow<GenericCameraFocus,FrameUpdatePolicy,10> window1( &sc );
	// A Camera that fills most of the window.
	SurfaceCamera camera1( "Camera 1", &driver1, 0xff, 0xff, 0xff,
		0, 30, 400, 210);
	// A Camera taking a small portion of the screen (10,10,60,60)
	SurfaceCamera camera2( "Camera 2", &driver2, 0xff,0xff,0xff, /*bkgcolor*/
		0, 0, 400, 30 );

	window1.cameras.add_surface_camera( &camera1 );
	camera1.assign_parent( &window1 );

	window1.cameras.add_surface_camera( &camera2 );
	camera2.assign_parent( &window1 );

	// All scene nodes are created dynamically to be automatically
	// freed at layer destruction
	
	TextLeaf test1( 20, 20, "Test1" );
	LayerNode txtLayer;
	txtLayer.add_child( heap.add( new SceneNode( &test1 ) ) );

	camera1.add_layer( &txtLayer );
	camera2.add_layer( &txtLayer );

	CameraSetEvent cse1( &camera1, 0,0, 60,40 );
	camera1.handle_event( &cse1 );
	CameraOnEvent cmr1_on( &camera1 );
	camera1.handle_event( &cmr1_on ); 

	CameraSetEvent cse2( &camera1, -100,-60, 140,100 );
	camera2.handle_event( &cse2 );
	CameraOnEvent cmr2_on( &camera2 );
	camera2.handle_event( &cmr2_on ); 


	
	SDL_Event event;
	bool quit = false;

	while( !quit ){
		while( SDL_PollEvent(&event) )
		{
			switch(event.type){
     			case SDL_QUIT:
         			quit = 1;
         			break;
     			default:
				Event *e = 
					SDLEventDriver::handle_event(&event);
				window1.handle_event( e );
				delete e;
					
         			break;
			}
		}
		window1.advance_time();
		window1.process_internal_events();
		window1.repaint();
		framecnt++;
		usleep( 2000 );
	}

	SDL_Quit();
	return 0;
}

Resulting scene:


Types of scene nodes

cZUI is built on top of 5 types of scene nodes: BaseSceneNode, SceneNode, LeafNode, GroupNode and LayerNode.

BaseSceneNode is the base of all the other nodes and contains event handling, boundrect test functions and node movement functions to be overriden by the nodes higher up in the hierarchy.

SceneNodes can have one child beneath them, allowing for composition, customizing existing nodes without a need to subclass or modify them. SceneNodes are often used to restrict/twist the flow of events to the nodes below or to draw additional appearance over them. Composition is a flexible tool borrowed from the Piccolo toolkit.

LeafNode is probably the first node presenting enough functionality to derive from, in order to create your own nodes. LeafNodes contain a (X,Y) position on the virtual canvas, perform translate and rotate, and provide limited event management (calling paint() on RepaintEvents).

GroupNodes are used to manipulate nodes together (translate, rotate etc.) They will also cache the bounding rectangle to reduce computation load at event handling. GroupNodes can be combined with other SceneNodes to achieve various effects. GroupNodes are the most versatile of all.

LayerNodes are GroupNodes that can be directly added to Camera, and can be toggled on/off dynamically.

Contributed nodes

In addition to these, the contrib directory contains useful nodes that can save you development time.

ActiveLeaf nodes are the most suitable base for a scene node. They will handle all events, including Repaint, RepaintRect, MouseOver, MouseMove, and MouseDown/Up events.

ContainerNodes are simple one-selection groups with notification of selection change.

CursorPolicyNodes are used to set cursor policy for a group of nodes without having to code cursor policy functionality into every node type.

DefaultCursorNodes can be used to set default cursor over an area.

EventFilter nodes can be used to allow/deny certain events to children nodes. AreaEventFilters can be used to filter out events over an area, for all its children.

HUDGroupNodes can be used to hold a group of nodes fixed to the screen, whatever the view of the Camera.


Cameras and windows

Cameras


Cameras control what will be drawn in a specific area of the screen, serve as context for events, and can move around showing portions of the scene. Cameras can overlap in the output window, and are drawn bottom-to-top.


Layers within cameras will be drawn bottom-to-top. Disabled cameras and layers do not accept events.

Windows

Windows in cZUI encapsulate the drawing area of a OS window. A window may hold many cameras, distinct or overlapped. Event handling will try the currently active window first, and, if not handled, will try all the cameras back to front. This behaviour can be changed by writing your own camera focus policy.
Redraw behaviour can also be changed. Currently available redraw policies are: framerate and always-update.

Views and drivers

Corresponding to every camera is a view that actually handles drawing and does the transformations for the camera. Thus, scene nodes should address all paint requests to the context's view.
There are two types of view: ScrollableView and ZoomableView. ZoomableView can be zoomed in addition to scrolling. The latter can be divided into ZoomableSurfaceView and ZoomableObjectView. ZoomableSurfaceView handles zooming and scrolling for the nodes transparently, i.e. it will transform the given coordinates and sizes as it handles the requests.
At the moment only the ZoomableSurfaceView has been implemented.

Surface/Event Drivers

SurfaceDrivers and EventDrivers are wrappers to a certain video/event library. You can write a wrapper for your platform/graphics library in a few days.

An advanced example

This example adds an image leaf next to the text. The
code for the leaf is below:
class ImageLeaf : public ActiveLeaf
{
protected:
	Image *normal, *hover;
	double image_zoom;
public:

	void paint( Camera * );
	void paint_hover( Camera * );
	void get_boundrect( double& double& double&
						double& );
						
	bool check_event_fitspixels( Event *e );

	ImageLeaf( double, double,
			Image*, Image*, double iz =1. );
};

ImageLeaf :: ImageLeaf( double px, double py, 
		Image *nr, Image *hv,
		double iz )
	: ActiveLeaf( px, py ),
	  normal( nr ), hover( hv ),
	  image_zoom(iz) 
{
}


void ImageLeaf :: paint( Camera *c )
{
	c->get_view()->put_image( normal, 
		(int)xpos, (int)ypos, image_zoom );
}

void ImageLeaf :: paint_hover( Camera *c )
{
	c->get_view()->put_image( hover, 
		(int)xpos, (int)ypos, image_zoom );
}


void ImageLeaf :: get_boundrect( double& rx1, double &ry1,
		double &rx2, double &ry2 )
{
	int w,h;
	normal->get_dimensions( w, h );
	w /= image_zoom;	// image_zoom specifies the scene zoom at 
	h /= image_zoom;	// which image displays normally
	rx1 = xpos; ry1 = ypos;
	rx2 = rx1 + w; ry2 = ry1 + h;
}

bool ImageLeaf :: check_event_fitspixels( Event *e )
{
	if( hover ) return 1;
	return 0;
}

// in main

	Image *arr_normal, *arr_hover, *tmp;
	tmp = camera1.get_view()->load_image( "./bmw_normal.bmp" );
	arr_normal = tmp->convert_to_565();
	delete tmp;
	tmp = camera1.get_view()->load_image( "./bmw.bmp" );
	arr_hover = tmp->convert_to_565();
	delete tmp;
	ImageLeaf testimg( 5,17, arr_normal, arr_hover, 10 );
	txtLayer.add_child( heap.add( new SceneNode( &testimg ) ) );



Here's the corresponding image: