Skip to content
Browse files

Added double-click @ 99% complete: 1% is the case of double-clicking …

…the minimum of the range, which better parseFloat (floor, ceil) might manage. Hopefully improved docs and dragging.
  • Loading branch information...
1 parent dfda6dd commit a4c3b956127aab6a8a0c3b8b325b8e9a471eb49f @leegee committed Oct 29, 2012
Showing with 3,066 additions and 95 deletions.
  1. +23 −9 Demo/index.html
  2. +2,932 −0 Demo/mootools-more.js
  3. +41 −33 README.md
  4. +70 −53 Source/Knob.js
View
32 Demo/index.html
@@ -9,8 +9,8 @@
<meta name="description" content="A rotary knob control for MooTools"/>
<meta name="keywords" content="knob, rotary, ui, mootools, javascript"/>
- <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.4/mootools-yui-compressed.js"></script>
-
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.4/mootools-yui-compressed.js"></script>
+ <script type="text/javascript" src="mootools-more.js"/></script>
<script type="text/javascript" src="../Source/Knob.js"></script>
<script type="text/javascript">
@@ -48,7 +48,10 @@
background-image: radial-gradient(12pt 12pt 45deg, circle cover, yellow 0%, orange 100%, red 95%);
}
- </style>
+ .mooknob-label {
+ color: black;
+ }
+ </style>
</head>
@@ -180,7 +183,7 @@
<section>
<h2>Inline Graphics</h2>
<p>
- Default values, with some quick graphics.
+ A range of zero to 10, with some quick graphics.
Added <code>data-scale=0.1</code> to allow greater mousemovements to have smaller effect,
so the user has to drag further to twist the knob.
Added <code>degreesoffset=180</code>, so that the pointer is initially pointing
@@ -191,19 +194,30 @@
<span id='bg' style='position:absolute;top:0;background:url(knob_bg_164x165.png); width:164px; height:165px'></span>
<span class='mooknob' id='fg'
data-addpointer='false'
- data-scale='0.1'
data-degreesoffset='180'
+ data-scale='0.1'
+ data-value='0'
+ aria-valuemin='0'
+ aria-valuemax='10'
style='position:absolute;top:0;left:0;background:url(knob_fg_164x165.png); width:164px; height:165px'></span>
</div>
</xmp>
<p style='position:absolute'>
Result:
- <span id='bg' style='position:absolute;top:0;left:200px;background:url(knob_bg_164x165.png); width:164px; height:165px'></span>
+ <span id='bg' style=' position:absolute;top:0;left:200px;background:url(knob_bg_164x165.png); width:164px; height:165px'></span>
<span class='mooknob' data-addpointer='false'
- data-scale='0.1'
+ data-scale='0.01'
+ data-value='0'
+ data-monitor='volume'
+ aria-valuemin='0'
+ aria-valuemax='10'
data-degreesoffset='180'
- id='fg' style='position:absolute;top:0;left:200px;background:url(knob_fg_164x165.png); width:164px; height:165px'></span>
- </div>
+ id='fg' style='outline: 0;position:absolute;top:0;left:200px;background:url(knob_fg_164x165.png); width:164px; height:165px'></span>
+
+ <input type='text' id='volume'
+ style='position:absolute; left: 400px; width:4em'
+ />
+ </p>
</section>
</body>
View
2,932 Demo/mootools-more.js
2,932 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
74 README.md
@@ -14,10 +14,11 @@ A *Knob* object can be instantiated with a variety of options,
and the library also parses the DOM for elements with class *.mooknob*,
replacing each with an instance of the Knob control.
-All knobs require user-supplied styling.
+All knobs require user-supplied styling: details below.
-Knobs can be controlled with keyboard and/or mouse, and provide
-WIA-ARIA attributes.
+Knobs can be controlled with cursor keys, mouse drags, and double clicks,
+and provide WIA-ARIA attributes populated from both the initial data,
+and computed data in real time.
Knobs can monitor and reflect a field with a *value* attribute.
@@ -28,20 +29,28 @@ control receives keyboard focus, the cursor keys may be used to
increment and decrement the value - by 10 if if the shift key is
depressed. If the alt/meta key is depressed whilst a cursor key is
pressed, the value of the control is set to its maximum or minimum,
-dependent on the cursor key..
+dependent on the cursor key.
+
+The effect of mouse dragging the control must be scaled using the
+option `scale`: that is, the number of pixels moved will be multiplied
+by the value of the `scale` option (or `data-scale` attribute) and added
+to the current value of the knob. If your knob runs from -1000 to 1000, you
+may not need to set the `scale`, but if you knob runs from 1 to 10, then a
+typical mouse movement of 100 pixels will have be useless without at least
+setting `scale` to at least 0.01. What an `autoscale` option be useful?
Monitoring
----------
-A control can monitor and reflect values of another element's *value* fileld: such element should be supplied via the *monotpr* field, as described in 'Options', below.
+A control can monitor and reflect values of another element's `value` fileld: such element should be supplied via the *monotpr* field, as described in 'Options', below.
Initial Control Value
---------------------
-The initial value of the control comes from either the *monitor* elements' value, or the *options.value* (which may come from the *data-value* attribute of the *element*), or from the element's *value* field, in that order.
+The initial value of the control comes from either the `monitor` elements' `value` attribute, or the `options.value` (which may come from the `data-value` attribute of the `element`), or from the element's `value` attribute, in that order.
-CSS
----
+Styling
+-------
As seems to be usual for MooTools, no CSS is supplied. However,
the Docs/index.html page contains some examples. A basic gray
@@ -63,30 +72,28 @@ supports the *border-radius* property of CSS3:
If select the *addpointer* option (below), the widget will contain a UTF-8 up-arrow.
-Use in HTML, without Javascript
--------------------------------
+WIA-ARIA And Use in HTML, without Javascript
+--------------------------------------------
The widget can be configued using the attribute of any element
-which it consumes.
+which it consumes. Any option listed under the JavaScript API
+can be passed as an HTML attribute, prefixed with `data-`.
-The range of values the widget will supports defaults to +/-100,
-but can be set with the aria-valuemin and aria-valuemax attributes
-of the element.
+The range of values the widget will supports (default ±100)
+can be set with the `aria-valuemin` and `aria-valuemax` attributes
+of the element, which equate to the object's `range` option array.
-The initial value can be set via the value attribute
-or data-value attribute.
-
-The chosen value of the control will be placed
-in the aria-valuenow and aria-valuetext attributes, and in the value
-attribute, if present, otherwise in the data-value attribute.
+The initial (described above) will be placed
+in the `aria-valuenow` and `aria-valuetext` attributes, as well as
+in the `value` attribute, if present, otherwise in the `data-value` attribute.
For example, the following element will produce a rotary knob with
-an up-arrow (), an initial value of 10, and range between -20 and 20:
+an up-arrow (`↑`), an initial value of 5, and range between 0 and 10:
<span id='knob1'
- data-value='10'
- aria-valuemin='-20'
- aria-valuemax='20'
+ data-value='5'
+ aria-valuemin='0'
+ aria-valuemax='10'
>↑</span>
JavaScript API
@@ -96,8 +103,8 @@ The equivalent to the above HTML would be:
new Knob({
element: 'knob1',
- range: [-20, 20],
- value: 10
+ value: 5,
+ range: [0, 10]
});
Options
@@ -128,15 +135,14 @@ In addition to the above options, the following events are supplied:
* `onTick`: fired as the knob is turned
The `onTick` event is intended to allow the user to adjust the behaviour
-of the widget, using the following object fields:
+of the widget, and to allow this widget to affect other objects,
+using the following object fields:
* `x` and `y` represent the position of the mouse curosr relative to the knob
-* `movement` contains the greater of these two,
-* `value` contains the previous value incremented by `movement` multiplied by the value of the `scale` option (*this.options.scale*).
-* `degrees` contains the amount by which the knob will be rotated, and can be set in accordance with the values accepted by the CSS3 Transform/rotate property (0-360, afik)
+* `movement` contains the greater of these two
+* `value` contains the calculated value of the knob
+* `degrees` contains the amount by which the knob will be rotated, after `onTick` has returned, and can be set in accordance with the value in degrees, as accepted by the CSS3 Transform/rotate property.
-The `onTick` event could just be used to update a text display field.
-
Public Methods
--------------
@@ -147,7 +153,9 @@ Public Methods
TO DO
-----
-* Double click support is nearly available, but how to convert an angle to a value?
+* Double click support seems to miss the very minimum value of the range.
+
+* Considering if the knob should support more intuative dragging, dependant upon the knob's current value.
FAQ
---
View
123 Source/Knob.js
@@ -10,13 +10,15 @@ authors:
requires:
- Core
- Element.Dimensions
+- Element.Measure
provides: [Knob]
...
*/
// # MooKnob
+// Version 0.6 - degrees-to-value for dblclick; better dragging: still no auto-scale for dragging
// Version 0.5 - degreesoffset, nearly got double-click support,
// Version 0.4 - Default pointer
// Version 0.2 - WIA-ARIA and keyboard control Support
@@ -100,11 +102,10 @@ var Knob = new Class({
dragging: false,
// For rendering
renderRange: null,
- /* // Positioning info for double-click-to-value
- dblClickAnchor: null,
- */
+ // Positioning info for double-click-to-value
+ dblClickAnchor: null,
-// ### Methods
+ // ### Methods
initialize: function( options, actx ){
var self = this;
@@ -115,7 +116,9 @@ var Knob = new Class({
: this.element = this.options.element;
// Required for keyboard focus
- this.element.setAttribute('tabIndex', this.element.getAttribute('tabIdex') || 0);
+ this.element.setAttribute('tabIndex',
+ this.element.getAttribute('tabIdex') || 0
+ );
// I was adding this manually a lot, and it was tedious
// to keep looking-up UTF-8 arrow characters
@@ -127,7 +130,7 @@ var Knob = new Class({
document.id(this.options.monitor)
: this.monitor = this.options.monitor;
}
-
+
var block = this.element.getStyle('display');
if (block=='inline' || block=='')
this.element.setStyle('display', 'inline-block');
@@ -146,7 +149,8 @@ var Knob = new Class({
// Needed when contxt is lost in GUI
this.element.store('self', this);
- this.renderRange = parseFloat(self.options.range[0]) * -1
+ this.renderRange = 1
+ + Math.abs( parseFloat(self.options.range[0]) )
+ Math.abs( parseFloat(self.options.range[1]) );
this.attach();
@@ -158,8 +162,7 @@ var Knob = new Class({
this.element.addEvents({
focus: this.focus,
blur: this.blur,
- /* dblclick: this.dblclick,
- */
+ dblclick: this.dblclick,
mousedown: this.mousedown
});
if (this.monitor) this.monitor.addEvent('change', this.monitorValueChange);
@@ -183,8 +186,7 @@ var Knob = new Class({
__ActiveMooToolsKnobCtrl__.element.removeEvents({
focus: __ActiveMooToolsKnobCtrl__.focus,
blur: __ActiveMooToolsKnobCtrl__.blur,
- /* dblclick: __ActiveMooToolsKnobCtrl__.dblclick,
- */
+ dblclick: __ActiveMooToolsKnobCtrl__.dblclick,
mousedown: __ActiveMooToolsKnobCtrl__.mousedown
});
@@ -200,41 +202,38 @@ var Knob = new Class({
destroy: function(){ this.detach() },
-// Double-click the control to set it's value:
-// this would make my GUI a lot easier, if only
-// I could convert the angle to a value.
-
-/*
+// Double-click the control to set it's value
dblclick: function(e){
var self = this.retrieve('self');
- if (!self.dblClickAnchor)
- self.dblClickAnchor = self.element.getCoordinates();
- var theta = self.angleBetween2Points( // Find the angle between the element centre and the click:
- [e.page.x - self.dblClickAnchor.left, e.page.y - self.dblClickAnchor.top ], // Co-ords of click within element:
- [self.dblClickAnchor.width/2, self.dblClickAnchor.height/2] // Centre of element is width/2, height/2
- );
- if (theta < 0) theta += 2 * Math.PI; // Keep radians positive
- var degrees = theta * (180 / Math.PI); // Convert to degrees
- degrees *= -1; // Invert to display
-
- console.log( degrees +' '+(degrees*-1) ); // degrees += self.options.degreesoffset;
+ // This is only set when needed, and reset incase of page resize
+ self.dblClickAnchor = self.element.getCoordinates();
- ? this.value = (degrees+this.options.degreesoffset) / this.renderRange;
-
- self.element.setStyles({
- 'transform': 'rotate('+degrees+'deg)',
- '-ms-transform': 'rotate('+degrees+'deg)',
- '-webkit-transform': 'rotate('+degrees+'deg)',
- '-o-transform': 'rotate('+degrees+'deg)',
- '-moz-transform': 'rotate('+degrees+'deg)',
- });
+ // Find the angle between the element centre and the click:
+ var degrees = ((self.angleBetween2Points(
+ // Co-ords of click within element:
+ [e.page.x - self.dblClickAnchor.left,
+ e.page.y - self.dblClickAnchor.top ],
+ // Centre of element is width/2, height/2
+ [self.dblClickAnchor.width/2,
+ self.dblClickAnchor.height/2]
+ )
+ // Adjust for display
+ - 180) * -1)
+ // + self.options.degreesoffset;
+
+ var stepPerDegree = self.renderRange / 360;
+ self.value = self.options.range[0] + stepPerDegree * degrees;
+
+ $('degrees').set('text', degrees);
+ $('value').set('text', stepPerDegree +' ... '+ self.value);
+
+ self.render();
},
-*/
angleBetween2Points: function ( point1, point2 ) {
var dx = point2[0] - point1[0];
var dy = point2[1] - point1[1];
- return Math.atan2( dx, dy );
+ return Math.atan2( dx, dy ) * (180 / Math.PI);
},
// Monitor changes in the .monitor field's value, and update control
@@ -317,17 +316,6 @@ var Knob = new Class({
self.fireEvent('mousedown');
},
-// Unsure how to bind the mouseup event to an object,
-// hence the ugly global
- mouseup: function(e){
- var self = __ActiveMooToolsKnobCtrl__;
- e.stop();
- window.removeEvent('mousemove', self.mousemove);
- window.removeEvent('mouseup', self.mouseup);
- self.dragging = false;
- self.fireEvent('mouseup');
- },
-
// Sets the x, y field as the position of the mouse curosr relative to the knob,
// sets the movement field as the greater of these two,
// and increments the value field by 'movement' multiplied by the value of
@@ -351,12 +339,41 @@ var Knob = new Class({
self.x = e.page.x - self.movementAnchor.x;
self.y = e.page.y - self.movementAnchor.y;
- /* var d = Math.sqrt( Math.pow(self.movementAnchor.x + self.x, 2) + Math.pow(self.movementAnchor.y + self.y, 2) ); */
+ //var d = Math.sqrt( Math.pow(self.movementAnchor.x + self.x, 2) + Math.pow(self.movementAnchor.y + self.y, 2) );
+
+ // self.movement = (Math.abs(self.x) > Math.abs(self.y)? self.x : self.y);
+
+ if (Math.abs(self.x) > Math.abs(self.y)){
+ self.movement = self.x;
+ if (self.x < self.movementAnchor.x)
+ self.movement *= -1;
+ }
+ else {
+ self.movement = self.y;
+ if (self.y < self.movementAnchor.y)
+ self.movement *= -1;
+ }
+
+ self.value = self.initialValue +
+ (self.movement * self.options.scale);
+
+ // console.log( self.initialValue +' moved by '+ (self.movement * self.options.scale) +' = '+self.value);
- self.movement = (Math.abs(self.x) > Math.abs(self.y)? self.x : self.y);
- self.value = self.initialValue + ( self.movement * self.options.scale);
self.render();
},
+
+// Cancel the movemouse/dragging events
+// Unsure how to bind the mouseup event to an object,
+// hence the ugly global
+ mouseup: function(e){
+ var self = __ActiveMooToolsKnobCtrl__;
+ e.stop();
+ window.removeEvent('mousemove', self.mousemove);
+ window.removeEvent('mouseup', self.mouseup);
+ self.dragging = false;
+ self.fireEvent('mouseup');
+ },
+
// Rotates the control knob.
// Requires this.value to be set.
@@ -432,7 +449,7 @@ Knob.parseDOM = function( selector ){
// Real issues
else {
console.log('Error setting '+i+' from dataset');
- console.info(e);
+ console.error(e);
}
}
});

0 comments on commit a4c3b95

Please sign in to comment.
Something went wrong with that request. Please try again.