Using SVG with XBL in XUL

From MozillaZine Knowledge Base
Jump to: navigation, search

The following example shows how to create an XBL component that uses SVG to render graphics. The code can be adapted as the basis of any number of vector-graphics based widgets.

The example consists of 4 files that are listed below. The first two files, svg_example.xul and svg_example.css, set up the example and consist of a simple XUL window and relevent CSS properties. The stylesheet svg_example.css also binds the XBL binding defined in svg-shape.xml to the svg-shape tag.

svg_example.xul

<?xml version="1.0"?>
<?xml-stylesheet href="svg_example.css" type="text/css"?>
<window title="Test"
	orient="horizontal"
	xmlns:html="http://www.w3.org/1999/xhtml" 
	xmlns:svg="http://www.w3.org/2000/svg" 
	xmlns:xlink="http://www.w3.org/1999/xlink"
	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
	<stack flex="1">
		<vbox flex="1">
			<svg-shape id="background-circle" flex="1" type="circle" />
		</vbox>
		<vbox flex="1">
			<spacer flex="2"/>
			<svg-shape flex="1" id="svg-button" type="rect" radius="12" label="Box"/>
			<spacer flex="2"/>
			<hbox flex="4">
				<svg-shape flex="1" id="circ1" type="circle" label="1"/>
				<svg-shape flex="1" id="circ2" type="circle" label="2" />
				<svg-shape flex="1" id="circ3" type="circle" label="3" />
			</hbox>
			<spacer flex="1"/>
		</vbox>
	</stack>

</window>

svg_example.css

window {
	background: #0D0D5A;
}

svg-shape {
	-moz-binding: url("svg-shape.xml#shape");
	-moz-user-focus: normal;
	stroke-width: 4px;
	font-family: Lucida Grande,Geneva,Verdana,Arial,Helvetica,sans-serif;
	font-style: bold;
	font-variant: normal;
	line-height: normal;
        font-size: 32px;
}

svg-shape .svg-shape-text {
	fill: white;
}

svg-shape .svg-shape-rect {
	fill: red;
	stroke: white;
}

svg-shape:focus .svg-shape-rect {
	fill: green;
	stroke: white;
}

svg-shape .svg-shape-circle {
	fill-opacity: .20;
	stroke: white;
}

svg-shape:focus .svg-shape-circle {
	fill-opacity: 1;
	stroke: white;
}

#circ1 .svg-shape-circle {
	fill: yellow;
}

#circ2 .svg-shape-circle {
	fill: green;
}

#circ3 .svg-shape-circle {
	fill: blue;
}

#svg-button {
        min-width: 48px;
        min-height: 48px;
        max-height: 48px;
}

#background-circle .svg-shape-circle {
	fill: lightslategray;
}


svg-shape.xml

<?xml version="1.0"?>
<bindings
	xmlns="http://www.mozilla.org/xbl" 
	xmlns:xbl="http://www.mozilla.org/xbl" 
	xmlns:html="http://www.w3.org/1999/xhtml" 
	xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
	xmlns:svg="http://www.w3.org/2000/svg" 
	xmlns:xlink="http://www.w3.org/1999/xlink"
>
	<binding id="shape">
		<implementation>
			<field name="svg_shape_box"/>
			<field name="svg_shape_type"/>
			<field name="svg_shape_rect"/>
			<field name="svg_shape_circle"/>
			<field name="svg_shape_ellipse"/>
			<field name="svg_shape_text"/>
			<field name="svg_shape_stroke"/>
			<field name="svg_shape_corner"/>
			<field name="svg_shape_loaded"/>
			<property name="disabled">
				<getter>
	<![CDATA[
	return this.getAttribute('disabled') == 'true';
	]]>
				</getter>
				<setter>
	<![CDATA[
  	if (val) {
	  	this.setAttribute('disabled', 'true');
  	}
  	else {
  		this.removeAttribute('disabled');
  	}
	return val;
	]]>
				</setter>
			</property>
			<property name="label">
				<setter>
	<![CDATA[
	this.setText(val);
	this.setAttribute("label", val);
	return val;
	]]>
				</setter>
			</property>
			<constructor>
	<![CDATA[
	
	const STROKE_WIDTH = 4;
	const CORNER_RADIUS = 0;
	
	this.svg_shape_type = this.getAttribute("type");
	if (!this.svg_shape_type) this.svg_shape_type = "rect";
	
	this.svg_shape_corner = parseInt(this.getAttribute("radius"));
	if (isNaN(this.svg_shape_corner)) this.svg_shape_corner = CORNER_RADIUS;
	
	//This should not be necessary, but unfortunately getting the value
	//from the stylesheet programmatically is not possible in current builds
	
	this.svg_shape_stroke = parseInt(this.getAttribute("stroke-width"));
	if (isNaN(this.svg_shape_stroke)) this.svg_shape_stroke = STROKE_WIDTH;

	var my_this = this;
	
	this.svg_shape_loaded = false;

	this.svg_shape_box = document.getAnonymousElementByAttribute(this, "anonid", "svg-shape-box");
	this.svg_shape_rect = document.getAnonymousElementByAttribute(this, "anonid", "svg-shape-rect");
	this.svg_shape_circle = document.getAnonymousElementByAttribute(this, "anonid", "svg-shape-circle");
	this.svg_shape_ellipse = document.getAnonymousElementByAttribute(this, "anonid", "svg-shape-ellipse");
	this.svg_shape_text = document.getAnonymousElementByAttribute(this, "anonid", "svg-shape-text");
	
	my_func = function myFunction(event){ window.setTimeout(function(xbl){ xbl.click(); }, 10, my_this); }
	this.svg_shape_box.addEventListener("click", my_func, true);

	my_func = function myFunction(){ my_this.doLayout();}
	window.addEventListener("load", my_func, true);

	]]>
			</constructor>
			<method name="doLayout">
				<body>
	<![CDATA[
	
	if (this.svg_shape_loaded) return;
	
	this.svg_shape_loaded = true;
	
	var box_w = this.boxObject.width;
	var box_h = this.boxObject.height;
	var cx = box_w / 2;
	var cy = box_h / 2;
			
	var stroke_w = this.svg_shape_stroke;
	
	//Either of the following should work, but don't
  	//var stroke_w = document.defaultView.getComputedStyle(this.svg_shape_rect, "").getPropertyCSSValue("stroke-width").getFloatValue(CSSPrimitiveValue.CSS_PX);
  	//var stroke_w = this.svg_shape_rect.getPresentationAttribute("stroke-width").getFloatValue(CSSPrimitiveValue.CSS_PX);
	
	this.svg_shape_box.setAttribute("width", box_w);
	this.svg_shape_box.setAttribute("height", box_h);
		
	if (this.svg_shape_type == "circle") {
		var r = Math.min(cx, cy);
		
		this.svg_shape_circle.setAttribute("cx", cx);
		this.svg_shape_circle.setAttribute("cy", cy);
		this.svg_shape_circle.setAttribute("r", r - stroke_w);
	
		this.svg_shape_rect.setAttribute("style", "display: none");
		this.svg_shape_ellipse.setAttribute("style", "display: none");
	}
	else if (this.svg_shape_type == "ellipse") {
		
		this.svg_shape_ellipse.setAttribute("cx", cx);
		this.svg_shape_ellipse.setAttribute("cy", cy);
		this.svg_shape_ellipse.setAttribute("rx", cx);
		this.svg_shape_ellipse.setAttribute("ry", cy);
	
		this.svg_shape_circle.setAttribute("style", "display: none");
		this.svg_shape_rect.setAttribute("style", "display: none");
	}
	else {
		this.svg_shape_rect.setAttribute("x", stroke_w / 2);
		this.svg_shape_rect.setAttribute("y", stroke_w / 2);
		this.svg_shape_rect.setAttribute("width", box_w - ((stroke_w / 2) + stroke_w));
		this.svg_shape_rect.setAttribute("height", box_h - ((stroke_w / 2) + stroke_w));
		this.svg_shape_rect.setAttribute("rx", this.svg_shape_corner);
		this.svg_shape_rect.setAttribute("ry", this.svg_shape_corner);

		this.svg_shape_circle.setAttribute("style", "display: none");
		this.svg_shape_ellipse.setAttribute("style", "display: none");
	}
	  	
	
	this.svg_shape_text.setAttribute("x", cx);
	this.svg_shape_text.setAttribute("y", cy - stroke_w);
		
  	this.setText(this.getAttribute("label"));

	//Not used but useful to know
	//var text_w = this.svg_shape_text.getComputedTextLength();	
  	//var text_h = document.defaultView.getComputedStyle(this.svg_shape_text, "").getPropertyCSSValue("font-size").getFloatValue(CSSPrimitiveValue.CSS_PX);

	]]>
				</body>
			</method>
			<method name="setText">
				<parameter name="text" /> 
				<body>
	<![CDATA[
	
	if (!text) text = "";
	this.svg_shape_text.firstChild.nodeValue = text;

	]]>
				</body>
			</method>
		</implementation>
		<resources>
			<stylesheet src="svg-shape.css" />
		</resources>
		<content>
			<xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient" align="center" pack="center" flex="1">
				<svg:svg anonid="svg-shape-box" width="10px" height="10px">
					<svg:g>
						<svg:rect anonid="svg-shape-rect" class="svg-shape-rect" x="2" y="2" width="10" height="10" rx="8" ry="8"/>
						<svg:circle anonid="svg-shape-circle" class="svg-shape-circle" cx="2" cy="2" r="10"/>
						<svg:ellipse anonid="svg-shape-ellipse" class="svg-shape-ellipse" cx="2" cy="2" rx="10" ry="10"/>
						<svg:text anonid="svg-shape-text" x="16" y="0" class="svg-shape-text">Text</svg:text>
					</svg:g>
				</svg:svg>
			</xul:hbox>
		</content>
	</binding>
</bindings>


svg-shape.css

.box-inherit {
	-moz-box-orient: inherit;
	-moz-box-pack: inherit;
	-moz-box-align: inherit;
	-moz-box-direction: inherit;
}

/* Text shape */

.svg-shape-text {
	font-weight: inherit;
	font-style: inherit;
	font-size: inherit;
	font-family: inherit;
	dominant-baseline: middle;
	text-anchor: middle;
}

/* Rect shape */

.svg-shape-rect {
	stroke-width: inherit;
}

/* Circle shape */

.svg-shape-circle {
	stroke-width: inherit;
}

/* Ellipse shape */

.svg-shape-ellipse {
	stroke-width: inherit;
}