Skip to content

Commit b9ff4c6

Browse files
author
Ian Prest
committed
Started implementation of rotated-cluster support.
-- Each key has a rotation angle, and center of rotation; available in the editor. -- Displaying crosshairs in the editor for the center of rotation. -- Keyboard shortcuts to move CoR, and change angle. -- Keyboard height is correctly calculated. TODO: -- Serialization is still wonky. -- Can't click on rotated keys to select them.
1 parent d828291 commit b9ff4c6

File tree

6 files changed

+172
-13
lines changed

6 files changed

+172
-13
lines changed

extensions.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,32 @@
2222
String.prototype.trim = function() { this.trimStart().trimEnd(); };
2323
}
2424

25+
26+
// a c e
27+
// b d f
28+
// 0 0 1
29+
function Matrix(a,b,c,d,e,f) { this.a = a || 1; this.b = b || 0; this.c = c || 0; this.d = d || 1; this.e = e || 0; this.f = f || 0; }
30+
Matrix.prototype.mult = function(Y) {
31+
return new Matrix(this.a*Y.a + this.c*Y.b, this.b*Y.a + this.d*Y.b, this.a*Y.c + this.c*Y.d, this.b*Y.c + this.d*Y.d, this.a*Y.e + this.c*Y.f + this.e, this.b*Y.e + this.d*Y.f + this.f);
32+
};
33+
Matrix.prototype.transformPt = function(pt) {
34+
return { x: this.a*pt.x + this.c*pt.y + this.e, y: this.b*pt.x + this.d*pt.y + this.f };
35+
};
36+
Math.Matrix = function(a,b,c,d,e,f) { return new Matrix(a,b,c,d,e,f); }
37+
Math.transMatrix = function(x,y) {
38+
return new Matrix(1, 0, 0, 1, x, y);
39+
}
40+
Math.rotMatrix = function(angleInDegrees) {
41+
var angleInRad = (angleInDegrees*Math.PI/180.0);
42+
var cos = Math.cos(angleInRad), sin = Math.sin(angleInRad);
43+
return new Matrix(cos, sin, -sin, cos, 0, 0);
44+
};
45+
2546
// Extend array objects with a last() function that returns the last element
2647
// in the array.
2748
if(!Array.prototype.last) {
2849
Array.prototype.last = function() {
2950
return this[this.length-1];
30-
}
51+
};
3152
}
32-
3353
}());

kb.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,18 @@ html, body {
190190
.hidden { display: none; }
191191
.error { border: solid 2px rgb(217,83,79); }
192192
#selectionRectangle { position: absolute; border: dashed 2px red; }
193+
#rotationCrosshairs {
194+
display: none;
195+
position: absolute;
196+
margin: 0px;
197+
padding: 0px;
198+
font-size: 25px;
199+
line-height: 23px;
200+
width: 25px;
201+
height: 25px;
202+
color: black;
203+
text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;
204+
}
193205

194206
#rawdata {
195207
width: 100%;
@@ -228,4 +240,4 @@ html, body {
228240
.hint:before, .hint:after, [data-hint]:before, [data-hint]:after { -webkit-transition: 0.1s ease; -moz-transition: 0.1s ease; transition: 0.1s ease; }
229241

230242
#swap-colors { position: relative; top: 15px; }
231-
.color-name { display: inline-block; min-width: 3em; font-size: 10px; white-space: nowrap; }
243+
.color-name { display: inline-block; min-width: 3em; font-size: 10px; white-space: nowrap; }

kb.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@
110110
'shift-right':'sizeKeys(.25,0,$event)',
111111
'shift-up':'sizeKeys(0,-.25,$event)',
112112
'shift-down':'sizeKeys(0,.25,$event)',
113+
'pageup':'rotateKeys(-15,$event)',
114+
'pagedown':'rotateKeys(15,$event)',
115+
'ctrl-left':'moveCenterKeys(-.25,0,$event)',
116+
'ctrl-right':'moveCenterKeys(.25,0,$event)',
117+
'ctrl-up':'moveCenterKeys(0,-.25,$event)',
118+
'ctrl-down':'moveCenterKeys(0,.25,$event)',
113119
delete:'deleteKeys()',
114120
insert:'addKey()',
115121
74: 'prevKey($event)',
@@ -129,6 +135,7 @@
129135

130136
<div ng-repeat="key in keys()"
131137
class="key {{key.profile}}"
138+
style="{{rotationStyle(key)}}"
132139
ng-mouseover="hoveredKey=key"
133140
ng-mouseleave="hoveredKey=null"
134141
ng-class="{hover: hoveredKey==key, selected: selectedKeys.indexOf(key)>=0}"
@@ -139,6 +146,7 @@
139146
{{calcKbHeight()}}
140147
</div>
141148
<div id="selectionRectangle" ng-style="{display:selRect.display, left:selRect.l+'px', width:selRect.w+'px', top:selRect.t+'px', height:selRect.h+'px'}"></div>
149+
<div id="rotationCrosshairs" ng-style="{display:multi.crosshairs, left:(keyboardLeft()+multi.crosshairs_x-5)+'px', top:(keyboardTop()+multi.crosshairs_y-7)+'px'}"><i class="icon-screenshot"></i></div>
142150

143151
<ul class="nav nav-tabs">
144152
<li ng-class="{active:selTab==0}"><a ng-click="selTab=0" data-toggle="tab"><i class="fa fa-edit"></i> Properties</a></li>
@@ -267,6 +275,14 @@
267275
<div class="color-name">{{colorName(multi.text)}}</div>
268276
</div>
269277
</div>
278+
<div class="control-group">
279+
<label class="control-label" for="angleeditor">Rotation:</label>
280+
<div class="controls hint--top hint--rounded" data-hint="Specify the angle (in degrees) by which to rotate the keycaps, and the x &amp; y coordinates of the center of rotation.">
281+
<input id="angleeditor" size="3" type="number" min="-180" max="180" step="15" ng-model="multi.rotation_angle" ng-change="updateMulti('rotation_angle')" ng-blur="validateMulti('rotation_angle')" ng-disabled="selectedKeys.length<1">&#176;
282+
<input id="rotxeditor" size="3" type="number" min="0" max="36" step=".25" ng-model="multi.rotation_x" ng-change="updateMulti('rotation_x')" ng-blur="validateMulti('rotation_x')" ng-disabled="selectedKeys.length<1">,
283+
<input id="rotxeditor" size="3" type="number" min="0" max="36" step=".25" ng-model="multi.rotation_y" ng-change="updateMulti('rotation_y')" ng-blur="validateMulti('rotation_y')" ng-disabled="selectedKeys.length<1">
284+
</div>
285+
</div>
270286
<div class="control-group">
271287
<label class="control-label">Misc:</label>
272288

@@ -375,6 +391,8 @@ <h5>Keyboard Shortcuts</h5>
375391
<tr><td><span class='shortcut'>?</span></td><td>Show this help dialog</td></tr>
376392
<tr><td><span class='shortcut'>↑↓←→</span></td><td>Move the selected keys</td></tr>
377393
<tr><td><span class='shortcut'>Shift&ndash;↑↓←→</span></td><td>Resize the selected keys</td></tr>
394+
<tr><td><span class='shortcut'>Ctrl&ndash;↑↓←→</span></td><td>Move the center of rotation for the selected keys</td></tr>
395+
<tr><td><span class='shortcut'>PgUp</span> / <span class='shortcut'>PgDn</span></td><td>Change the angle of rotation for the selected keys</td></tr>
378396
<tr><td><span class='shortcut'>Ins</span></td><td>Add a new key</td></tr>
379397
<tr><td><span class='shortcut'>Del</span></td><td>Delete the selected keys</td></tr>
380398
<tr><td><span class='shortcut'>F2</span></td><td>Edit the text of the selected key</td></tr>

kb.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,13 @@
167167
$scope.calcKbHeight = function() {
168168
var bottom = 0;
169169
$scope.keys().forEach(function(key) {
170-
bottom = Math.max(bottom, key.renderBottom);
170+
bottom = Math.max(bottom, key.rectTrans.y2);
171171
});
172172
$scope.kbHeight = bottom + 8;
173173
};
174174

175175
// Given a key, generate the HTML needed to render it
176+
$scope.rotationStyle = $renderKey.getKeyRotationStyles;
176177
function renderKey(key) {
177178
key.html = $sce.trustAsHtml($renderKey.html(key,$sanitize));
178179
}
@@ -298,6 +299,9 @@
298299
height2 : function() { return Math.max(0.5, Math.min(12, value)); },
299300
fontheight : function() { return Math.max(1, Math.min(9, value)); },
300301
fontheight2 : function() { return Math.max(1, Math.min(9, value)); },
302+
rotation_angle : function() { return Math.max(-180, Math.min(180, value)); },
303+
rotation_x : function() { return Math.max(0, Math.min(36, value)); },
304+
rotation_y : function() { return Math.max(0, Math.min(36, value)); },
301305
};
302306
return (v[prop] || v._)();
303307
}
@@ -321,6 +325,7 @@
321325
}
322326
}
323327
},
328+
rotation_angle : function() { key.rotation_angle = value; key.rotation_x = $scope.multi.rotation_x; key.rotation_y = $scope.multi.rotation_y; },
324329
};
325330
return (u[prop] || u._)();
326331
}
@@ -441,6 +446,37 @@
441446
});
442447
if(y!==0) { $scope.calcKbHeight(); }
443448
};
449+
$scope.rotateKeys = function(angle,$event) {
450+
$event.preventDefault();
451+
if($scope.selectedKeys.length<1) {
452+
return;
453+
}
454+
transaction("rotate", function() {
455+
$scope.selectedKeys.forEach(function(selectedKey) {
456+
var newangle = (selectedKey.rotation_angle+angle+360)%360;
457+
while(newangle > 180) { newangle -= 360; }
458+
update(selectedKey, 'rotation_angle', newangle);
459+
renderKey(selectedKey);
460+
});
461+
$scope.multi = angular.copy($scope.selectedKeys.last());
462+
});
463+
$scope.calcKbHeight();
464+
};
465+
$scope.moveCenterKeys = function(x,y,$event) {
466+
$event.preventDefault();
467+
if($scope.selectedKeys.length<1) {
468+
return;
469+
}
470+
transaction("moveCenter", function() {
471+
$scope.selectedKeys.forEach(function(selectedKey) {
472+
update(selectedKey, 'rotation_x', validate(selectedKey, 'rotation_x', $scope.multi.rotation_x + x));
473+
update(selectedKey, 'rotation_y', validate(selectedKey, 'rotation_y', $scope.multi.rotation_y + y));
474+
renderKey(selectedKey);
475+
});
476+
$scope.multi = angular.copy($scope.selectedKeys.last());
477+
});
478+
$scope.calcKbHeight();
479+
};
444480

445481
$scope.loadPalette = function(p) {
446482
$scope.palette = p;
@@ -802,8 +838,11 @@
802838
});
803839
});
804840
};
805-
$scope.canCopy = function() { return $scope.selectedKeys.length > 0; }
806-
$scope.canPaste = function() { return clipboard.length > 0; }
841+
$scope.canCopy = function() { return $scope.selectedKeys.length > 0; };
842+
$scope.canPaste = function() { return clipboard.length > 0; };
843+
844+
$scope.keyboardTop = function() { var kbElem = $("#keyboard"); return kbElem.position().top + parseInt(kbElem.css('margin-top'),10); };
845+
$scope.keyboardLeft = function() { var kbElem = $("#keyboard"); return kbElem.position().left + parseInt(kbElem.css('margin-left'),10); };
807846
}]);
808847

809848
// Modernizr-inspired check to see if "color" input fields are supported;

render.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ var $renderKey = {};
1212
c.l = Math.min(100,c.l*mod);
1313
return c.sRGB8();
1414
}
15+
16+
$renderKey.getKeyRotationStyles = function(key) {
17+
if(key.rotation_angle == 0) {
18+
return "";
19+
}
20+
var angle = key.rotation_angle.toString() + "deg";
21+
var origin = (sizes.capsize(key.rotation_x) + sizes.margin).toString() + "px " + (sizes.capsize(key.rotation_y) + sizes.margin).toString() + "px";
22+
return "transform: rotate("+angle+"); -ms-transform: rotate("+angle+"); -webkit-transform: rotate("+angle+"); " +
23+
"transform-origin: "+origin+"; -ms-transform-origin: "+origin+"; -webkit-transform-origin: "+origin+";";
24+
};
1525

1626
// Given a key, generate the HTML needed to render it
1727
var noRenderText = [0,2,1,3,0,4,2,3];
@@ -84,9 +94,46 @@ var $renderKey = {};
8494
html += "</div></div>";
8595
}
8696

87-
key.rect = { x:capx, y:capy, w:capwidth, h:capheight };
88-
key.rect2 = { x:capx2, y:capy2, w:capwidth2, h:capheight2 };
89-
key.renderBottom = Math.max(capy + capheight, capy2 + capheight2);
97+
key.rect = { x:capx, y:capy, w:capwidth, h:capheight, x2:capx+capwidth, y2:capy+capheight };
98+
key.rect2 = { x:capx2, y:capy2, w:capwidth2, h:capheight2, x2:capx2+capwidth2, y2:capy2+capheight2 };
99+
key.rectMax = { x:Math.min(capx,capx2), y:Math.min(capy,capy2), x2:Math.max(capx+capwidth,capx2+capwidth2), y2:Math.max(capy+capheight,capy2+capheight2) };
100+
key.rectMax.w = key.rectMax.x2 - key.rectMax.x;
101+
key.rectMax.h = key.rectMax.y2 - key.rectMax.y;
102+
103+
// Rotation matrix about the origin
104+
var origin_x = sizes.capsize(key.rotation_x)+sizes.margin;
105+
var origin_y = sizes.capsize(key.rotation_y)+sizes.margin;
106+
var mat = Math.transMatrix(origin_x, origin_y).mult(Math.rotMatrix(key.rotation_angle)).mult(Math.transMatrix(-origin_x, -origin_y));
107+
108+
// Construct the *eight* corner points, transform them, and determine the transformed bbox.
109+
key.rectTrans = { x:9999999, y:9999999, x2:-9999999, y2:-9999999 };
110+
var corners = [
111+
{x:key.rect.x, y:key.rect.y},
112+
{x:key.rect.x, y:key.rect.y2},
113+
{x:key.rect.x2, y:key.rect.y},
114+
{x:key.rect.x2, y:key.rect.y2},
115+
{x:key.rect2.x, y:key.rect2.y},
116+
{x:key.rect2.x, y:key.rect2.y2},
117+
{x:key.rect2.x2, y:key.rect2.y},
118+
{x:key.rect2.x2, y:key.rect2.y2},
119+
];
120+
for(var i = 0; i < corners.length; ++i) {
121+
corners[i] = mat.transformPt(corners[i]);
122+
key.rectTrans.x = Math.min(key.rectTrans.x, corners[i].x);
123+
key.rectTrans.y = Math.min(key.rectTrans.y, corners[i].y);
124+
key.rectTrans.x2 = Math.max(key.rectTrans.x2, corners[i].x);
125+
key.rectTrans.y2 = Math.max(key.rectTrans.y2, corners[i].y);
126+
}
127+
key.rectTrans.w = key.rectTrans.x2 - key.rectTrans.x;
128+
key.rectTrans.h = key.rectTrans.y2 - key.rectTrans.y;
129+
130+
key.crosshairs = "none";
131+
if(key.rotation_x || key.rotation_y || key.rotation_angle) {
132+
key.crosshairs_x = sizes.capsize(key.rotation_x) + sizes.margin;
133+
key.crosshairs_y = sizes.capsize(key.rotation_y) + sizes.margin;
134+
key.crosshairs = "block";
135+
}
136+
90137
return html;
91138
};
92139
}());

serial.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ var $serial = {};
2222

2323
// function to sort the key array
2424
$serial.sortKeys = function(keys) {
25-
keys.sort(function(a,b) {
26-
return (a.y - b.y) ||
25+
keys.sort(function(a,b) {
26+
return ((a.rotation_angle+360)%360 - (b.rotation_angle+360)%360) ||
27+
(a.rotation_x - b.rotation_x) ||
28+
(a.rotation_y - b.rotation_y) ||
29+
(a.y - b.y) ||
2730
(a.x - b.x);
2831
});
2932
};
@@ -34,6 +37,7 @@ var $serial = {};
3437
width: 1, height: 1, width2: 1, height2: 1, // size
3538
color: "#cccccc", text: "#000000", // colors
3639
labels:[], align: 4, fontheight: 3, fontheight2: 3, // label properties
40+
rotation_angle: 0, rotation_x: 0, rotation_y: 0, // rotation
3741
profile: "", nub: false, ghost: false, stepped: false // misc
3842
};
3943
};
@@ -43,20 +47,39 @@ var $serial = {};
4347
var keys = keyboard.keys;
4448
var rows = [], row = [];
4549
var current = $serial.defaultKeyProps();
50+
var rowmeta = {r:0,rx:0,ry:0};
51+
var lastrowmeta = angular.copy(rowmeta);
4652
if(keyboard.meta) {
4753
var meta = angular.copy(keyboard.meta);
4854
if(meta.backcolor === '#eeeeee') { delete meta.backcolor; }
4955
if(!$.isEmptyObject(meta)) {
5056
rows.push(meta);
5157
}
5258
}
59+
function pushRow() {
60+
if(rowmeta.r != lastrowmeta.r || rowmeta.rx != lastrowmeta.rx || rowmeta.ry != lastrowmeta.ry) {
61+
rows.push(rowmeta);
62+
lastrowmeta = angular.copy(rowmeta);
63+
}
64+
rows.push(row);
65+
current.y++;
66+
current.x = rowmeta.rx;
67+
row = [];
68+
};
69+
5370
$serial.sortKeys(keys);
5471
keys.forEach(function(key) {
5572
var props = {};
5673
var label = key.labels.join("\n").trimEnd();
5774

5875
// start a new row when necessary
59-
if(key.y !== current.y) { rows.push(row); row = []; current.y++; current.x = 0; }
76+
if((row.length>0) && (key.y !== current.y || key.rotation_angle != rowmeta.r || key.rotation_x != rowmeta.rx || key.rotation_y != rowmeta.ry)) {
77+
pushRow();
78+
}
79+
80+
if(key.rotation_angle != rowmeta.r) { rowmeta.r = key.rotation_angle; }
81+
if(key.rotation_x != rowmeta.rx) { current.x = rowmeta.rx = key.rotation_x; }
82+
if(key.rotation_y != rowmeta.ry) { current.y = rowmeta.ry = key.rotation_y; }
6083

6184
function serializeProp(nname,val,defval) {
6285
if(val !== defval) {
@@ -89,7 +112,7 @@ var $serial = {};
89112
if(!jQuery.isEmptyObject(props)) { row.push(props); }
90113
row.push(label);
91114
});
92-
if(row.length>0) { rows.push(row); }
115+
if(row.length>0) { pushRow(); }
93116
return rows;
94117
}
95118

0 commit comments

Comments
 (0)