Commits

Daniel Herzog  committed bb8ba77 Merge

Merged

  • Participants
  • Parent commits 5f6f476, 6b6d3c4

Comments (0)

Files changed (8)

 <li><a href="./ui-elements/contextmenu/index.html">contextmenu</a></li>
 <li><a href="./ui-elements/horizontal-navigation/index.html">horizontal-navigation</a></li>
 <li><a href="./ui-elements/resource-metadata-drawer/index.html">resource-metadata-drawer</a></li>
+<li><a href="./ui-elements/ruler/index.html">ruler</a></li>
 <li><a href="./ui-elements/sortable-table/index.html">sortable-table</a></li>
 <li><a href="./ui-elements/toolbar-buttons/index.html">toolbar-buttons</a></li>
 
 <li><a href="./tests/syntax-highlight-css/index.html">syntax-highlight-css</a></li>
 <li><a href="./tests/syntax-highlight-javascript/index.html">syntax-highlight-javascript</a></li>
 <li><a href="./tests/syntax-highlight-markup/index.html">syntax-highlight-markup</a></li>
+<li><a href="./tests/key-identifier/index.html">key-identifier</a></li>
 
 </ul>
 </body>

File tests/js-highlight-performance/index.html

 <link rel="stylesheet" href="../../resources/ui-style/syntax-highlight.css">
 <link rel="alternate stylesheet" title="t1" href="./dragonfly.css">
 <style>
-body
+html, body
 {
   margin: 0;
   padding: 0;
+  height: 100%;
 }
 form
 {
 }
 .source
 {
-  position: relative;
+  position: absolute;
+  top: 220px;
+  bottom: 0px;
+  width: 100%;
   white-space: pre;
   font-family: monospace;
   font-size: 12px;
   line-height: 15px;
   border-top: 1px solid hsl(0, 0%, 70%);
+  overflow: auto;
 }
 .js-source 
 {
-  margin-left: 10ex;
+  padding-left: 10ex;
 }
 .line-numbers
 {
 
 
 <div id="header">
-  <form>
+  <form onsubmit="return false">
     <textarea id="output" rows="4" cols="70"></textarea>
     <p><label><input type="checkbox" id="make-line-numbers" checked> with line numbers</label>
        <label><input type="checkbox" onchange="enable_stylesheets(this.checked)"> enable Dragonfly stylesheet</label>
                             <option>10</option>
                             <option>50</option>
                           </select></label>
+       <label><input type="text">
+              <input type="button" 
+                     value="scroll to" 
+                     onclick="go_to_line(Number(this.previousElementSibling.value))" 
+                     ></label>
     <div></div>
   </form>
   <div id="controls">loading 1.5 MB js source file ...</div>

File tests/js-highlight-performance/testhighlight.js

   }, 0);
 };
 
+var go_to_line = function(line)
+{
+  var container = document.getElementsByClassName('js-source')[0];
+  if (container)
+  {
+    const TEXT = document.TEXT_NODE;
+    var count = 0;
+    var span = document.createElement('span');
+    span.textContent = ' ';
+    var tops = [];
+    var child = container.firstChild;
+    while (child)
+    {
+      if (child.nodeType == TEXT)
+      {
+        var pos = -1;
+        while (true)
+        {
+          pos = child.nodeValue.indexOf('\n', pos + 1);
+          if (pos == -1)
+            break;
+          count++;
+          if (count == line)
+          {
+            var target_pos = child.splitText(pos);
+            child.parentNode.insertBefore(span, target_pos);
+            tops.push(span.getBoundingClientRect().top);
+            child.parentNode.removeChild(span);
+            if (tops.length < 2)
+            {
+              line += 2;
+            }
+            else
+            {
+              var scroll_container = document.getElementsByClassName('source')[0];
+              var container_top = scroll_container.getBoundingClientRect().top;
+              var delta = tops[1] - tops[0];
+              var scroll_top = scroll_container.scrollTop;
+              container.style.cssText = 
+                "background:" +
+                  "-o-linear-gradient(90deg, hsl(220, 100%, 85%) 0px, " +
+                                            "hsl(220, 100%, 85%) " + delta + "px, " +
+                                            "transparent " + delta + "px) 0 0;" +
+                "background-size: 100% " + delta + "px;" +
+                "background-position: 0 " + 
+                  (tops[0] - container_top + scroll_top) + "px;" +
+                "background-repeat: no-repeat;";
+              scroll_container.scrollTop = scroll_top + tops[0] - container_top;
+              child.parentNode.normalize();
+              return;
+            }
+          }
+        }
+      }
+      child = child && child.nextSibling;
+    }
+  }
+};
+
 labels["template"] =
 [
   "tokenize and create template",

File tests/key-identifier/index.html

+<!doctype html>
+<script>
+if (!(function(){}).bind)
+{
+  Function.prototype.bind = function (context)
+  {
+    var method = this, args = Array.prototype.slice.call(arguments, 1);
+    return function()
+    {
+      return method.apply(context, args.concat(Array.prototype.slice.call(arguments)));
+    }
+  };
+};
+</script>
+
+<script src="https://bitbucket.org/scope/dragonfly-stp-1/raw/69f36c8c0f5c/src/ui-scripts/actions/keyidentifier.js"></script>
+<script>
+
+var onshortcut = function(shortcut, event)
+{
+  document.getElementsByTagName('p')[0].textContent = shortcut;
+  var pre = document.getElementsByTagName('pre')[0];
+  
+  var pos = (', ' + pre.textContent + ',').indexOf(', ' + shortcut + ',');
+  var selection = getSelection();
+  var range = document.createRange();
+  selection.removeAllRanges();
+  if (pos > -1)
+  {
+    range.setStart(pre.firstChild, pos);
+    range.setEnd(pre.firstChild, pos + shortcut.length);
+    selection.addRange(range);
+  }
+  event.stopPropagation();
+  event.preventDefault();
+}
+
+window.onload = function()
+{
+  var sc = 
+  [
+    "ctrl a", "ctrl b", "shift ctrl a", 
+    "up","down","left","right",
+    "enter","ctrl enter",
+    "shift tab","tab", "ctrl tab",
+    "escape", "shift escape",
+    "a", "(", ")", "s", "f6", ".", "[",
+    "ctrl p", "page-down", "page-up",
+    "cmd k", "cmd a", "cmd b", "cmd s",
+    "cmd f", "cmd shift f", "cmd w",
+    "cmd z", "cmd shift z"
+  ];
+  var ki = new KeyIdentifier(onshortcut, window.chrome ? "chrome" : window.opera ?"opera" : "firefox");
+  document.getElementsByTagName('pre')[0].textContent = sc.join(', ');
+  ki.set_shortcuts(sc);
+}
+
+</script>
+<pre style="width: 200px; margin: auto; white-space: pre-wrap;"></pre>
+<p style="font-family: Garamond, serif; font-size: 5em; text-align: center; color: #666;">

File ui-elements/ruler/index.html

+<!doctype html>
+<link rel="stylesheet" href="./style.css">
+<style>
+#container
+{
+	position: absolute;
+	left: 20px;
+	top: 150px;
+	width: 700px;
+	height: 700px; 
+	background-image: 
+		-o-linear-gradient(45deg, hsl(0, 0%, 0%) 0, hsl(220, 100%, 50%) 100%);
+}
+
+</style>
+<script src="../../resources/scripts/dom.js"></script>
+<script src="./ruler.js"></script>
+<script src="./templates.js"></script>
+<script>
+
+window.onload = function()
+{
+	window.ruler = new cls.Ruler(function(ruler)
+	{
+		document.getElementsByTagName('code')[0].textContent = 
+		"width: " + ruler.w + "px\nheight: " + ruler.h + "px";
+	});
+};
+
+var show_ruler = function()
+{
+	window.ruler.show_ruler(document.getElementById('container'));
+};
+
+document.addEventListener('input', function(event)
+{
+	switch (event.target.name)
+	{
+		case 'scale':
+		{
+			window.ruler.scale = Number(event.target.value);
+			break;
+		}
+	}
+}, false)
+
+</script>
+<div id="controls">
+<form>
+	<p><input type="button" value="Show ruler" onclick="show_ruler()">
+	<p><label>scale: <input type="range"
+	                        name="scale"
+	                        min="1"
+	                        max="30"
+	                        value="1"></label>
+</form>
+<p><code></code>
+</div>
+<div id="container"></div>

File ui-elements/ruler/ruler.js

+window.cls || (window.cls = {});
+
+cls.Ruler = function(callback)
+{
+	this._init(callback);
+};
+
+cls.Ruler.BASE_CLASS = "ruler";
+cls.Ruler.CLOSE_BUTTON_CLASS = "ruler-close";
+
+window.addEventListener('load', function()
+{
+	[
+		['BORDER_LEFT', '.ruler-left-bg', 'width'],
+		['BORDER_TOP', '.ruler-top-bg', 'height'],
+		['BORDER_RIGHT', '.ruler-right-bg', 'width'],
+		['BORDER_BOTTOM', '.ruler-bottom-bg', 'height'],
+	].forEach(function(a)
+	{
+		const TARGET = 0, CLASS = 1, PROP = 2;
+		var val = document.styleSheets.getDeclaration (a[CLASS])[a[PROP]];
+		cls.Ruler[a[TARGET]] = parseInt(val);
+	});
+
+	cls.Ruler.prototype = new function()
+	{
+		/* interface */
+
+		this.show_ruler = function(container){};
+		this.hide_ruler = function(){};
+		this.set_container = function(container){};
+		this.callback; // setter and getter
+		this.onclose; // setter and getter
+		this.scale; // setter and getter
+		this.w; // getter
+		this.h; // getter
+
+		/* constants */
+
+		const BASE_CLASS = cls.Ruler.BASE_CLASS;
+		const CLOSE_BUTTON_CLASS = cls.Ruler.CLOSE_BUTTON_CLASS;
+		const BORDER_LEFT = cls.Ruler.BORDER_LEFT;
+		const BORDER_TOP = cls.Ruler.BORDER_TOP;
+		const BORDER_RIGHT = cls.Ruler.BORDER_RIGHT;
+		const BORDER_BOTTOM = cls.Ruler.BORDER_BOTTOM;
+		const INTERVAL = 30;
+
+		this._init = function(callback)
+		{
+			this._callback = callback;
+			this.base_class = BASE_CLASS;
+			this._scale = 1;
+			this._onnodeinserted_bound = this._onnodeinserted.bind(this);
+			this._onmousedown_bound = this._onmousedown.bind(this);
+			this._onmouseup_bound = this._onmouseup.bind(this);
+			this._onmousemove_bound = this._onmousemove.bind(this);
+			this._onclick_bound = this._onclick.bind(this);
+			this._update_bound = this._update.bind(this);
+			this._update_handler = null;
+			this._interval = 0;
+			this._event = 0;
+			this._rx0 = 50;
+			this._rx1 = 350;
+			this._ry0 = 50;
+			this._ry1 = 350;
+			this._cur_w = 0;
+			this._cur_h = 0;
+		};
+
+		// event handlers
+
+		this._onnodeinserted = function(event)
+		{
+			if (event.target.hasClass(this.base_class))
+			{
+				document.removeEventListener('DOMNodeInserted',
+				                             this._onnodeinserted_bound,
+				                             false);
+				this._setup(event.target);
+			}
+		};
+
+		this._onmousedown = function(event)
+		{
+			var handler = this._get_handlers(event);
+			if (handler && !this._interval)
+			{
+				this._update_handler = handler;
+				document.addEventListener('mouseup',
+			                             this._onmouseup_bound,
+			                             false);
+			   document.addEventListener('mousemove',
+		                                this._onmousemove_bound,
+		                                false);
+		      this._interval = setInterval(this._update_bound, INTERVAL);
+		      event.stopPropagation();
+		      event.preventDefault();
+			}
+			else
+			{
+				this._onmouseup();
+			}
+		};
+
+		this._onmouseup = function(event)
+		{
+			document.removeEventListener('mouseup',
+			                             this._onmouseup_bound,
+			                             false);
+			document.removeEventListener('mousemove',
+			                             this._onmousemove_bound,
+			                             false);
+			clearInterval(this._interval);
+			this._interval = 0;
+			this._event = null;
+		};
+
+		this._onmousemove = function(event)
+		{
+			this._event = event;
+		};
+
+		this._onclick = function(event)
+		{
+			if (event.target.hasClass(CLOSE_BUTTON_CLASS))
+			{
+				this.hide_ruler();
+			}
+		};
+
+		this._update = function()
+		{
+			this._update_handler();
+		};
+
+		this._move_handler = function()
+		{
+			if (this._event)
+			{
+				var rx0 = this._snap_x0(this._event.clientX - this._ev_delta_x);
+				var rx1 = rx0 + this._rx1 - this._rx0;
+				var ry0 = this._snap_y0(this._event.clientY - this._ev_delta_y);
+				var ry1 = ry0 + this._ry1 - this._ry0;
+				this._redraw_ruler(rx0, rx1, ry0, ry1);
+			}
+		};
+
+		this._snap = function(number)
+		{
+			var candidate = (number / this._scale >> 0) * this._scale;
+			if ((number % this._scale) / this._scale > .5)
+			{
+				candidate += this._scale;
+			}
+			return candidate;
+		};
+
+		this._snap_x0 = function(cand)
+		{
+			cand = this._snap(cand + BORDER_LEFT) - BORDER_LEFT;
+
+			// ensure that rx1 is snapped
+			if (!((this._rx1 - BORDER_RIGHT) % this._scale == 0))
+			{
+				this._rx1 = this._snap(this._rx1 - BORDER_RIGHT) + BORDER_RIGHT;
+				while (this._rx1 > this._max_x)
+				{
+					this._rx1 -= this._scale;
+				}
+			}
+
+			var scaled = ((this._min_x + BORDER_LEFT) / this._scale) >> 0;
+			var min_left = (scaled + 1) * this._scale - BORDER_LEFT;
+			var width = this._rx1 - this._rx0;
+			scaled = (this._max_x - BORDER_RIGHT) / this._scale >> 0;
+			var max_right = scaled * this._scale + BORDER_RIGHT - width;
+			while (cand < min_left)
+			{
+				cand += this._scale;
+			}
+			while (cand > max_right)
+			{
+				cand -= this._scale;
+			}
+			return cand;
+		};
+
+		this._snap_y0 = function(cand)
+		{
+			cand = this._snap(cand + BORDER_TOP) - BORDER_TOP;
+
+			// ensure that rx1 is snapped
+			if (!((this._ry1 - BORDER_BOTTOM) % this._scale == 0))
+			{
+				this._ry1 = this._snap(this._ry1 - BORDER_BOTTOM) + BORDER_BOTTOM;
+				while (this._ry1 > this._max_y)
+				{
+					this._ry1 -= this._scale;
+				}
+			}
+
+			var scaled = ((this._min_y + BORDER_TOP) / this._scale) >> 0;
+			var min_top = (scaled + 1) * this._scale - BORDER_TOP;
+			var width = this._ry1 - this._ry0;
+			scaled = (this._max_y - BORDER_BOTTOM) / this._scale >> 0;
+			var max_bottom = scaled * this._scale + BORDER_BOTTOM - width;
+			while (cand < min_top)
+			{
+				cand += this._scale;
+			}
+			while (cand > max_bottom)
+			{
+				cand -= this._scale;
+			}
+			return cand;
+		};
+
+		this._snap_x1 = function(cand)
+		{
+			cand = this._snap(cand - BORDER_RIGHT) + BORDER_RIGHT;
+			var width = this._rx1 - this._rx0;
+			var min_left = this._rx0 + BORDER_LEFT + BORDER_RIGHT;
+			var scaled = (this._max_x - BORDER_RIGHT) / this._scale >> 0;
+			var max_right = scaled * this._scale + BORDER_RIGHT;
+			while (cand < min_left)
+			{
+				cand += this._scale;
+			}
+			while (cand > max_right)
+			{
+				cand -= this._scale;
+			}
+			return cand;
+		};
+
+		this._snap_y1 = function(cand)
+		{
+			cand = this._snap(cand - BORDER_BOTTOM) + BORDER_BOTTOM;
+			var width = this._ry1 - this._ry0;
+			var min_top = this._ry0 + BORDER_TOP + BORDER_BOTTOM;
+			var scaled = (this._max_y - BORDER_BOTTOM) / this._scale >> 0;
+			var max_bottom = scaled * this._scale + BORDER_BOTTOM;
+			while (cand < min_top)
+			{
+				cand += this._scale;
+			}
+			while (cand > max_bottom)
+			{
+				cand -= this._scale;
+			}
+			return cand;
+		};
+
+		this._width_handler = function()
+		{
+			if (this._event)
+			{
+				var rx1 = this._snap_x1(this._event.clientX - this._ev_delta_x);
+				this._redraw_ruler(null, rx1);
+				this._call_callback();
+			}
+		};
+
+		this._height_handler = function()
+		{
+			if (this._event)
+			{
+				var ry1 = this._snap_y1(this._event.clientY - this._ev_delta_y);
+				this._redraw_ruler(null, null, null, ry1);
+				this._call_callback();
+			}
+		};
+
+		this._width_and_height_handler = function()
+		{
+			if (this._event)
+			{
+				var rx1 = this._snap_x1(this._event.clientX - this._ev_delta_x);
+				var ry1 = this._snap_y1(this._event.clientY - this._ev_delta_y);
+				this._redraw_ruler(null, rx1, null, ry1);
+				this._call_callback();
+			}
+		};
+
+		this._call_callback = function(force)
+		{
+			if (this._callback)
+			{
+				if (force || this._cur_w != this.w || this._cur_h != this.h)
+				{
+					this._cur_w = this.w;
+					this._cur_h = this.h;
+					this._callback(this);
+				}
+			}
+		};
+
+		this._get_handlers = function(event)
+		{
+			var box = this._set_max_dimensions();
+			if (box)
+			{
+				var evx = event.clientX - box.left;
+				var evy = event.clientY - box.top;
+				// move
+				if ((evx >= this._rx0 && evx < this._rx1 &&
+				     evy > this._ry0 && evy < this._ry0 + BORDER_TOP) ||
+				    (evx >= this._rx0 && evx <= this._rx0 + BORDER_RIGHT &&
+				     evy >= this._ry0 + BORDER_TOP && evy <= this._ry1))
+				{
+					this._ev_delta_x = event.clientX - this._rx0;
+					this._ev_delta_y = event.clientY - this._ry0;
+					return this._move_handler;
+				}
+				// width
+				if (evx >= this._rx1 - BORDER_RIGHT && evx <= this._rx1 &&
+				     evy >= this._ry0 + BORDER_TOP && evy <= this._ry1 - BORDER_BOTTOM)
+				{
+					this._ev_delta_x = event.clientX - this._rx1;
+					return this._width_handler;
+				}
+				// height
+				if (evx >= this._rx0 + BORDER_LEFT && evx <= this._rx1 - BORDER_RIGHT &&
+				     evy > this._ry1 - BORDER_BOTTOM && evy <= this._ry1)
+				{
+					this._ev_delta_y = event.clientY - this._ry1;
+					return this._height_handler;
+				}
+				// width and height
+				if (evx >= this._rx1 - BORDER_RIGHT && evx <= this._rx1 &&
+				    evy > this._ry1 - BORDER_BOTTOM && evy <= this._ry1)
+				{
+					this._ev_delta_x = event.clientX - this._rx1;
+					this._ev_delta_y = event.clientY - this._ry1;
+					return this._width_and_height_handler;
+				}
+
+			}
+			return null;
+		};
+
+		this._setup = function(ruler_ele)
+		{
+			this._ruler_ele = ruler_ele;
+			this._set_max_dimensions();
+			this._container.addEventListener('mousedown',
+		                                    this._onmousedown_bound,
+		                                    false);
+			this._container.addEventListener('click',
+		                                    this._onclick_bound,
+		                                    false);
+		   this._call_callback(true);
+		};
+
+		this._redraw_ruler = function(rx0, rx1, ry0, ry1)
+		{
+			var is_dirty = false;
+			if (typeof rx0 == 'number' && this._rx0 != rx0)
+			{
+				is_dirty = true;
+				this._rx0 = rx0;
+			} 
+			if (typeof rx1 == 'number' && this._rx1 != rx1)
+			{
+				is_dirty = true;
+				this._rx1 = rx1;
+			} 
+			if (typeof ry0 == 'number' && this._ry0 != ry0)
+			{
+				is_dirty = true;
+				this._ry0 = ry0;
+			} 
+			if (typeof ry1 == 'number' && this._ry1 != ry1)
+			{
+				is_dirty = true;
+				this._ry1 = ry1;
+			} 
+
+			if (is_dirty)
+			{
+				this._ruler_ele.re_render(window.templates.ruler(this))
+				this._ruler_ele = this._container.getElementsByClassName(this.base_class)[0];
+			}
+		};
+
+		this._set_max_dimensions = function()
+		{
+			if (this._container)
+			{
+				var box = this._container.getBoundingClientRect();
+				this._min_x = -20;
+				this._min_y = -40;
+				this._max_x = box.width + BORDER_RIGHT;
+				this._max_y = box.height;
+				return box;
+			}
+			return null;
+		};
+
+		this.__defineSetter__('scale', function(scale)
+		{
+			this._scale = scale;
+			if (this._ruler_ele)
+			{
+				var rx0 = this._snap_x0(this._rx0);
+				var ry0 = this._snap_y0(this._ry0);
+				this._redraw_ruler(rx0, null, ry0);
+				this._call_callback();
+			}
+		});
+
+		this.__defineGetter__('scale', function() {return this._scale});
+		this.__defineSetter__('left', function(x) {});
+		this.__defineGetter__('left', function() {return this._rx0;});
+		this.__defineSetter__('top', function(x) {});
+		this.__defineGetter__('top', function() {return this._ry0;});
+		this.__defineSetter__('right', function(x) {});
+		this.__defineGetter__('right', function() {return this._rx1;});
+		this.__defineSetter__('bottom', function(x) {});
+		this.__defineGetter__('bottom', function() {return this._ry1;});
+		this.__defineSetter__('width', function(x) {});
+		this.__defineGetter__('width', function() {return this._rx1 - this._rx0;});
+		this.__defineSetter__('height', function(x) {});
+		this.__defineGetter__('height', function() {return this._ry1 - this._ry0;});
+		this.__defineSetter__('target_width', function(x) {});
+		this.__defineGetter__('target_width', function() 
+		{
+			return this._rx1 - this._rx0 - BORDER_LEFT - BORDER_RIGHT;
+		});
+
+		this.__defineSetter__('target_height', function(x) {});
+		this.__defineGetter__('target_height', function() 
+		{
+			return this._ry1 - this._ry0 - BORDER_TOP - BORDER_BOTTOM;
+		});
+		
+		this.__defineSetter__('w', function(x) {});
+		this.__defineGetter__('w', function() {return this.target_width / this._scale;});
+		this.__defineSetter__('h', function(x) {});
+		this.__defineGetter__('h', function() {return this.target_height / this._scale;});
+
+		this.show_ruler = function(container)
+		{
+			if (!this._ruler_ele)
+			{
+				this._container = container;
+				document.addEventListener('DOMNodeInserted',
+				                             this._onnodeinserted_bound,
+				                             false);
+				this._set_max_dimensions();
+				this._rx0 = this._snap_x0(this._rx0);
+				this._ry0 = this._snap_y0(this._ry0);
+				this._container.render(window.templates.ruler(this));
+			}
+		};
+
+		this.hide_ruler = function()
+		{
+			if (this._ruler_ele && this._ruler_ele.parentNode)
+			{
+				this._ruler_ele.parentNode.removeChild(this._ruler_ele);
+				this._ruler_ele = null;
+				this._container.removeEventListener('mousedown',
+			                                       this._onmousedown_bound,
+			                                       false);
+				this._container.removeEventListener('click',
+			                                       this._onclick_bound,
+			                                       false);
+			}
+		}
+	};
+
+}, false);

File ui-elements/ruler/style.css

+.ruler
+{
+	position: absolute;
+}
+
+.ruler-top-bg
+{
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 40px;
+	background-color: hsla(0, 0%, 0%, .5);
+	border-radius: 3px 3px 0 0;
+}
+
+.ruler-right-bg
+{
+	position: absolute;
+	right: 0;
+	top: 40px;
+	bottom: 15px;
+	width: 15px;
+	background-color: hsla(0, 0%, 0%, .5);
+}
+
+.ruler-bottom-bg
+{
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	height: 15px;
+	background-color: hsla(0, 0%, 0%, .5);
+	border-radius: 0 0 3px 3px;
+}
+
+.ruler-left-bg
+{
+	position: absolute;
+	top: 40px;
+	left: 0;
+	bottom: 15px;
+	width: 40px;
+	background-color: hsla(0, 0%, 0%, .5);
+}
+
+.ruler-top-scale
+{
+	position: absolute;
+	top: 0;
+	left: 39px;
+	right: 14px;
+	height: 40px;
+	background-image:
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px),
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px),
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px);
+	background-position: 0 37px, 0 34px, 0 31px;
+	background-repeat: repeat-x;
+}
+
+.ruler-bottom-scale
+{
+	position: absolute;
+	bottom: 0;
+	left: 39px;
+	right: 14px;
+	height: 15px;
+	background-image:
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px),
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px),
+		-o-linear-gradient(0deg, #fff 0, #fff  1px, transparent 1px);
+	background-position: 0 0;
+	background-repeat: repeat-x;
+}
+
+.ruler-left-scale
+{
+	position: absolute;
+	top: 39px;
+	left: 0;
+	bottom: 14px;
+	width: 40px;
+	background-image:
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px),
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px),
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px);
+	background-position: 37px 0, 34px 0, 31px 0;
+	background-repeat: repeat-y;
+}
+
+.ruler-right-scale
+{
+	position: absolute;
+	top: 39px;
+	right: 0;
+	bottom: 14px;
+	width: 15px;
+	background-image:
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px),
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px),
+		-o-linear-gradient(-90deg, #fff 0, #fff 1px, transparent 1px);
+	background-position: 0 0;
+	background-repeat: repeat-y;
+}
+
+.ruler-labels-top
+{
+	white-space: nowrap;
+	text-align: center;
+	padding-left: 40px;
+	position: absolute;
+	left: 0;
+	right: 0;
+	top: 10px;
+	overflow: hidden;
+	color: #fff;
+}
+
+.ruler-label-top
+{
+	display: inline-block;
+	font-size: 10px;
+	font-family: Arial, sans-serif;
+}
+
+.ruler-labels-left
+{
+	text-align: right;
+	padding-top: 40px;
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	top: 0;
+	width: 30px;
+	overflow: hidden;
+	color: #fff;
+}
+
+.ruler-label-left
+{
+	display: block;
+	font-size: 10px;
+	font-family: Arial, sans-serif;
+	width: 30px;
+}
+
+.ruler-close
+{
+	display: block;
+	position: absolute;
+	right: 0;
+	top: 0;
+	width: 17px;
+	height: 14px;
+	border-radius: 2px;
+	background-image:
+		-o-linear-gradient(45deg, transparent 0,
+		                          transparent 5px,
+			                       #fff 5px,
+			                       #fff 7px,
+			                       transparent 7px),
+		-o-linear-gradient(-45deg, transparent 0,
+		                          transparent 5px,
+			                       #fff 5px,
+			                       #fff 7px,
+			                       transparent 7px);
+   background-size: 9px 8px;
+   background-position: 4px 3px;
+   background-repeat: no-repeat;
+}

File ui-elements/ruler/templates.js

+
+(function()
+{
+
+	const MIN_WIDTH = 30;
+	const SCALES = {};
+
+	SCALES[50] = [50, 10, 2];
+	SCALES[20] = [20, 10, 1];
+	SCALES[10] = [10, 5, 1];
+	SCALES[5] = [5, 1, 1];
+	SCALES[1] = [10, 5, 1];
+
+	this.ruler = function(ruler)
+	{
+		var label_width = [50, 20, 10, 5, 1].reduce(function(scale, iter)
+		{
+			return iter * ruler.scale > MIN_WIDTH ? iter : scale;
+		}, 50);
+		var pixel_width = label_width * ruler.scale;
+		var scales = this._get_scales(label_width, ruler.scale);
+		
+		return (
+		['div',
+			['div', 'class', 'ruler-top-bg'],
+			['div', 'class', 'ruler-right-bg'],
+			['div', 'class', 'ruler-bottom-bg'],
+			['div', 'class', 'ruler-left-bg'],
+			this._ruler_top_labels(label_width, pixel_width, ruler.target_width),
+			this._ruler_left_labels(label_width, pixel_width, ruler.target_height),
+			this._ruler_top_scale(scales),
+			this._ruler_right_scale(scales),
+			this._ruler_bottom_scale(scales),
+			this._ruler_left_scale(scales),
+			['span', 'class', cls.Ruler.CLOSE_BUTTON_CLASS],
+			'style', 'top:' + ruler.top + 'px;' +
+			         'left:' + ruler.left + 'px;' +
+			         'width:' + ruler.width + 'px;' +
+			         'height:' + ruler.height + 'px;',
+			'class', cls.Ruler.BASE_CLASS]);
+	};
+
+	this._get_scales = function(label_width, scale)
+	{
+		return SCALES[label_width].map(function(s){return ruler.scale * s});
+	};
+
+	this._ruler_top_scale = function(scales)
+	{
+		var style = "background-size: " + scales[2] + "px 3px, " +
+		                                  scales[1] + "px 6px, " +
+		                                  scales[0] + "px 10px;";
+		return ['div', 'style', style, 'class', 'ruler-top-scale'];
+	};
+
+	this._ruler_bottom_scale = function(scales)
+	{
+		var style = 
+		"background-size: " + scales[2] + "px 3px, " +
+		                      scales[1] + "px 6px, " +
+		                      scales[0] + "px 10px;";
+		return ['div', 'style', style, 'class', 'ruler-bottom-scale'];
+	};
+
+	this._ruler_left_scale = function(scales)
+	{
+		var style = "background-size: 3px " + scales[2] + "px, " +
+		                             "6px " + scales[1] + "px, " +
+		                             "9px " + scales[0] + "px;";
+		return ['div', 'style', style, 'class', 'ruler-left-scale'];
+	};
+
+	this._ruler_right_scale = function(scales)
+	{
+		var style = "background-size: 3px " + scales[2] + "px, " +
+		                             "6px " + scales[1] + "px, " +
+		                             "9px " + scales[0] + "px;";
+		return ['div', 'style', style, 'class', 'ruler-right-scale'];
+	};
+
+	this._ruler_top_labels = function(label_width, pixel_width, max_width)
+	{
+		var labels = ['div'], i = 0;
+		while (true)
+		{
+			labels.push(
+			['span', 
+				i * pixel_width <= max_width ? String(i * label_width) : '\u00A0', 
+				'style', 'width: ' + pixel_width + 'px;' +
+				         (!i ? "margin-left: -" + (pixel_width / 2 >> 0) + "px;" : ""),
+				'class', 'ruler-label-top']);
+			if (i * pixel_width > max_width + pixel_width)
+			{
+				break;
+			}
+			i++;
+		}
+		labels.push('class', 'ruler-labels-top');
+		return labels;
+	};
+
+	this._ruler_left_labels = function(label_width, pixel_width, max_width)
+	{
+		var labels = ['div'], i = 0;
+		while (true)
+		{
+			labels.push(
+			['span', 
+				i * pixel_width <= max_width ? String(i * label_width) : '\u00A0', 
+				'style', 'height: ' + pixel_width + 'px;' +
+				         'line-height: ' + pixel_width + 'px;' +
+				         (!i ? "margin-top: -" + (pixel_width / 2 >> 0) + "px;" : ""),
+				'class', 'ruler-label-left']);
+			if (i * pixel_width > max_width + pixel_width)
+			{
+				break;
+			}
+			i++;
+		}
+		labels.push('class', 'ruler-labels-left');
+		return labels;
+	};
+
+}).apply(window.templates || (window.templates = {}));